munotes®

Wireless and Sensor Networks Practical Notes | B.Sc. (Computer Science) Semester 5 | Mumbai University | munotes

Get access to whole semester resourcesSemester Pass

Official Notes munotes.in

Wireless and Sensor Networks Practical

B.SC. (COMPUTER SCIENCE) · SEMESTER 5

Strictly as per the University of Mumbai NEP syllabus in force for B.Sc. (Computer Science)

For B.Sc. (Computer Science) students of the University of Mumbai and all its affiliated colleges

Open the book ↓

munotes.in Third Year

Wireless and Sensor Networks Practical

Copyright © 2026 munotes.in. All rights reserved.

Written and first published by munotes.in, 2026.

This book is free for individual students to read at munotes.in. No part of it may be reproduced, distributed, stored, translated or used for institutional or classroom purposes in any form without a prior written licence from munotes.in.

Licensing and permissions: contact@munotes.in

The text of statutes and of judgments reproduced in this book is in the public domain under section 52(1)(q) of the Copyright Act 1957. The commentary, arrangement, examples and questions are the original work of munotes.in.

munotes.in is an independent study resource for MU students. It is not affiliated with, endorsed by, or officially connected to the University of Mumbai. Course names and university references describe the students and syllabus the material relates to.

munotes.in

Contents

Module I TinyOS, nesC and TOSSIM: the sensor node, the execution model, radio and serial communication, flooding and routing tables

  1. How This Practical Is Examined: the Journal, the 80 Per Cent Rule and the Two-Hour Paper 1
  2. The TinyOS Laboratory from Zero: nesC, TinyOS 2.1.2 and TOSSIM on Ubuntu 6
  3. Practical 1: The Sensor Node Hardware Architecture 17
  4. Practical 2: Sensor Motes, a Base Station and Data Aggregation 27
  5. Practical 3: The TinyOS Architecture and Its Non-Preemptive Scheduler 38
  6. Practical 4: The nesC Programming Model: Modules, Configurations and Wiring 47
  7. Practical 5: Events, Commands, Tasks and Split-Phase Execution 55
  8. Practical 6: Simulating a Single Mote in TOSSIM 61
  9. Practical 7: Mote-to-Mote Radio Communication: Signal Strength and Packet Loss 69
  10. Practical 8: Mote-to-PC Serial Communication Through the SerialForwarder 78
  11. Practical 9: A Simple Ad Hoc Network in TOSSIM and Its Broadcast 86
  12. Practical 10: Routing Table Generation and Analysis 96

Module II NS-2 and the network: MANET routing with AODV and DSR, CSMA/CA, TDMA and duty-cycling MACs, directional antennas, coverage, and a cellular request

  1. The Network Simulator Laboratory from Zero: NS-2, Tcl, the Trace File and NAM 106
  2. Practical 11: A Basic MANET with Packet Animation 122
  3. Practical 12: The AODV Routing Protocol 134
  4. Practical 13: The DSR Routing Protocol and Its Overhead against AODV 148
  5. Practical 14: Performance Evaluation of MANET Routing Protocols 158
  6. Practical 15: The CSMA/CA MAC Protocol 168
  7. Practical 16: TDMA Slot Allocation and Its Energy against CSMA 178
  8. Practical 17: An Energy-Aware Duty-Cycling MAC for Sensor Networks 186
  9. Practical 18: A MANET with Directional Antennas 196
  10. Practical 19: Sensor Network Deployment and Coverage Analysis 202
  11. Practical 20: A Cellular Network and One Web Request through It 208
  12. The Two-Hour Paper: Sitting the Examination 221
munotes.in

Module I

TinyOS, nesC and TOSSIM: the sensor node, the execution model, radio and serial communication, flooding and routing tables

munotes.in

Chapter One

How This Practical Is Examined: the Journal, the 80 Per Cent Rule and the Two-Hour Paper

Syllabus topic Module 1 and Module 2, with MU's assessment rules for a 2-credit practical course: "Certified Journal is compulsory for appearing at the time of Practical Exam", "Minimum 80% practical are required to be completed.", "Mid - Term Practical Examination 15 marks", "Final Journal: 5 marks".

Aim

To know, before building a single sensor network, exactly how this paper is marked, what the journal must contain, what the two hours in the examination hall hold, and which laboratory each half of the paper is done in.

Why this chapter comes first

Every later chapter teaches you to do something. This one tells you what the marks are for.

Marks are lost on this paper by students whose programs work, because two of MU's rules are about the journal and one of them decides whether you may sit the examination at all. And this paper has a feature most practical papers do not: its two modules are done in two different laboratories, one a sensor-network operating system and its simulator, the other a network simulator. A student who is fluent in one and a stranger to the other has prepared for half an examination.

Where this paper sits in your semester

This is the practical paper of a Major Elective. MU's credit structure for Semester V prints it as MJELP2: Wireless & Sensor Networks Practical, 2 credits, and prints its theory paper beside it as MJEL2: Wireless & Sensor Networks, also 2 credits. The elective carries four credits in all, two for the theory paper and two for this practical, and the pair stands opposite the other pair on offer, Software Testing and Quality Assurance and its practical.

So the theory paper and this one are chosen together. The theory paper explains why sensor networks, ad hoc routing and MAC protocols are built the way they are. This paper makes you build, run and measure them. Each chapter here recalls the theory its exercise needs, and no more.

The paper in one table

Marks
InternalMid-term practical examination15
InternalFinal journal5
ExternalQ.1, a practical question on Module 115
ExternalQ.2, a practical question on Module 215
Total50

The external examination lasts two hours and carries 30 of the 50 marks. The other 20 are internal, and they are settled before you walk into the hall.

The paper is 2 credits. MU counts one practical credit as 30 hours of laboratory work, so the paper has 60 hours, thirty for each module.

The two rules that can stop you sitting the paper

"Certified Journal is compulsory for appearing at the time of Practical Exam." Those are MU's words. A journal becomes certified when your subject teacher has checked it and signed it. Arrive on the day of the practical examination without a certified journal and no program you can write will fix the problem.

"Minimum 80% practical are required to be completed." MU prints twenty practicals, ten in each module. Eighty per cent of twenty is sixteen. At least sixteen of the twenty must be done, written up and signed.

munotes.in1

How This Practical Is Examined: the Journal, the 80 Per Cent Rule and the Two-Hour Paper

Sixteen is the floor, not the plan. The external paper sets one question on Module 1 and one on Module 2, and nobody knows in advance which of the ten each will be drawn from. A student who skipped four practicals skipped four of the twenty things the examiner chooses from. Do all twenty.

What "completed" means for one practical

A practical is complete when your journal carries all of this for it:

  1. The practical number and its title, in MU's own words.
  2. Aim. A line or two saying what the exercise is for.
  3. Theory. What you need to know to do it, kept short: this is a practical journal, not a theory answer.
  4. Procedure. The steps, numbered.
  5. The program. For a Module 1 practical that means every file: the nesC configuration, the nesC module, the Makefile and the Python script that drives the simulation. For a Module 2 practical it means the Tcl script and the awk script that reads its trace, or the program itself.
  6. The output, exactly as the machine produced it.
  7. Observations. The table of what was measured. This is the part most students leave out and the part an examiner reads first.
  8. Result. One or two lines saying what the run showed.
  9. The date and your teacher's signature.

Every chapter of this book ends with a section called For the journal listing what that practical's write-up has to contain, in that order.

The two laboratories

MU names the tools in the paper's own description: "Using tools such as TinyOS, nesC, TOSSIM, and network simulators". This book uses exactly those, and runs every one of them.

Module 1 is done in TinyOS. TinyOS is an operating system for the tiny battery-powered computers called motes. Its programs are written in nesC, a dialect of C built for it, and TOSSIM is its simulator: the same nesC program is compiled to run on your PC, with as many simulated motes as you like and a simulated radio between them. You need no hardware. The next chapter sets the laboratory up from nothing on Ubuntu 22.04.

Module 2 is done in NS-2, the network simulator. You describe a network in a Tcl script (the nodes, how they move, the routing protocol, the MAC protocol and the traffic), NS-2 runs it and writes a trace file recording every packet, and you measure the network by reading that file, usually with awk. NAM, the network animator, plays the trace back as an animation. Chapter thirteen sets it up. Three Module 2 exercises also need a short Python program, and the last needs a small real network, both of which run on the same Ubuntu.

munotes.in2

How This Practical Is Examined: the Journal, the 80 Per Cent Rule and the Two-Hour Paper

Every program in this book was run on Ubuntu 22.04, and every output printed beside a program is what the machine produced. Where your own output differs, the chapter says what may differ and why.

The two-hour paper: how to spend the time

Two hours, two questions, fifteen marks each: an hour a question. The two questions come from the two laboratories, so the hour for Q.1 is spent in nesC and TOSSIM and the hour for Q.2 in Tcl and a trace file.

A working plan, not a rule of MU's:

MinutesWhat you are doing
0 to 5Read both questions. Decide which one you are surer of.
5 to 12Write the aim, the theory and the procedure for that question.
12 to 45Type the program, build it, run it, fix it, and get output.
45 to 55Write the output and the observation table into the answer.
55 to 110The same for the other question.
110 to 120Check both: output written down, table filled, result stated.

Start with the question you are surer of. One practical that runs and is written up completely is worth more than two that are half done.

Type the short files first. A TinyOS application is two nesC files, a Makefile and a Python script, and the Makefile and the configuration are a few lines each. An NS-2 answer is one Tcl script whose first thirty lines are the same in every wireless simulation. Chapters two and thirteen give you those skeletons; know them well enough to type them without looking.

Never leave the output blank. If the build fails, copy the first error line the machine printed, say in one sentence what you think it means, and show what you changed. An honest error with a diagnosis reads far better than an empty page.

What the examiner is actually looking at

The examiner has fifteen marks to give for one practical question. A fair division, and the one this book is written to satisfy:

PartWeight
The program is correct and runshigh
The output is present and matches the programhigh
The observation table and what it showsmedium
Aim, theory and procedure written outmedium
You can explain what your own program didmedium

The last line is not a separate viva in this paper. MU's pattern for a 2-credit practical under this scheme prints no viva component: the whole fifteen marks belong to the practical question. But an examiner standing at your machine will ask about what is on the screen, and your answer is part of how the fifteen marks are decided. So every chapter ends with Questions you must be able to answer, which are the questions asked at a machine: what does this line do, why did you choose this value, what happens if I change it.

munotes.in3

How This Practical Is Examined: the Journal, the 80 Per Cent Rule and the Two-Hour Paper

A warning about copying

Twenty students, one laboratory, the same twenty practicals. It is obvious to an examiner when four journals carry the same nesC module with the same variable names and the same comment in the same place, and it is just as obvious when the student in front of them cannot say what a line of it does.

Type the programs. Run them. Break one on purpose and read the error. That is what makes the questions at the machine easy, and nothing else prepares you for this paper as well.

The shape of every chapter after this one

The book follows MU's printed order: this chapter, the TinyOS laboratory, Practicals 1 to 10, the NS-2 laboratory, Practicals 11 to 20, and a last chapter on the examination itself. Every practical chapter is laid out the same way, so that you can read it straight into your journal:

  • Aim. MU's own words for the exercise.
  • What you need to know before you start. The theory, cut to what the exercise needs.
  • The steps, each with its program and its real output.
  • Procedure. The steps again as a numbered list, for the journal.
  • Observations, filled in from the run.
  • Result. What the run showed.
  • Where marks are lost. The mistakes that actually cost marks in that exercise.
  • For the journal. What to write.
  • Quick revision. A few lines for the day before.
  • Questions you must be able to answer.

Result

The assessment of this paper was recorded from MU's print: 20 internal marks, made up of a mid-term practical examination for 15 and the final journal for 5; a Semester End Practical Examination of two hours for 30 marks, with one practical question on each module for 15 marks each; a certified journal compulsory for appearing; and at least sixteen of the twenty practicals completed. The paper is MJELP2, chosen together with its theory paper MJEL2, and its two modules are done in TinyOS with TOSSIM and in NS-2.

Where marks are lost

An uncertified journal. Get each practical signed in the week you do it, not in the week before the examination.

Fewer than sixteen practicals. Count them. Twenty minus four is the line, and it is a hard line.

A Module 1 answer with one file missing. A TinyOS application does not build without its configuration, its module and its Makefile, and it does not run without the Python script. Write all four.

munotes.in4

How This Practical Is Examined: the Journal, the 80 Per Cent Rule and the Two-Hour Paper

No observation table. Most of the Module 2 exercises ask you to measure or compare: throughput, delivery ratio, delay, overhead, energy, coverage. A simulation that runs but measures nothing has done half the exercise.

Output not in the journal. The program is not the practical. The program, its output and what you concluded from the output are the practical.

Ninety minutes on one question. Half the paper is in the other laboratory.

For the journal

This chapter is not one of the twenty practicals and does not go into the journal. Use it for the index page of your practical file: list the twenty practicals in MU's order, tick each one when it is signed, and count the ticks.

Quick revision

  • Paper MJELP2, 2 credits, 60 hours, 50 marks, chosen with its theory paper MJEL2.
  • Internal 20: mid-term practical examination 15, final journal 5.
  • External 30: two hours, Q.1 on Module 1 for 15, Q.2 on Module 2 for 15.
  • A certified journal is compulsory for appearing at the practical examination.
  • At least 80 per cent of the practicals completed: sixteen of twenty.
  • Module 1 is TinyOS, nesC and TOSSIM. Module 2 is NS-2, with NAM and awk.
  • No separate viva marks are printed for this paper.
  • Every write-up: aim, theory, procedure, program, output, observations, result, signature.

Questions you must be able to answer

1. How many marks is this paper out of, and how are they split? Fifty. Twenty internal, made up of a mid-term practical examination for 15 and the final journal for 5, and thirty external in a two-hour practical examination.

2. What is in the two-hour examination? Two practical questions, one on Module 1 and one on Module 2, fifteen marks each.

3. How many practicals must be completed? At least eighty per cent of the twenty MU prints, which is sixteen.

4. What happens if your journal is not certified? MU's rule is that a certified journal is compulsory for appearing at the practical examination.

5. Which tools does each module use? Module 1 uses TinyOS, its language nesC and its simulator TOSSIM. Module 2 uses a network simulator, NS-2 in this book, with NAM to animate its trace and awk to measure it.

6. Which theory paper goes with this practical? Wireless & Sensor Networks, MJEL2. The two are chosen together as the four credits of the Semester V elective.

7. Is there a viva? No separate viva marks are printed for this paper. The whole fifteen marks of each question belong to the practical, and the questions an examiner asks at your machine are part of how those marks are decided.

8. Your TinyOS build fails with ten minutes left. What do you write? The aim, the theory, every file as far as you have it, the first error line the build printed, and one sentence saying what you think it means. Never an empty output.

Contents This chapter on its own page

munotes.in5

Chapter Two

The TinyOS Laboratory from Zero: nesC, TinyOS 2.1.2 and TOSSIM on Ubuntu

Syllabus topic Module 1, and the paper's own description: "Using tools such as TinyOS, nesC, TOSSIM, and network simulators, students explore sensor node architecture, routing protocols, MAC mechanisms, and network performance evaluation in dynamic wireless environments."

Aim

To install the nesC compiler, TinyOS 2.1.2 and its TOSSIM simulator on Ubuntu 22.04, to build TinyOS's Blink application for the simulator and run it, to build the same application for a real mote, and to meet, understand and fix the three problems every student on a current Ubuntu meets on the way.

What you need to know before you start

A mote is a very small computer built to be left somewhere and forgotten: a microcontroller, a low-power radio, one or more sensors and a pair of batteries. It has kilobytes of memory where a laptop has gigabytes, no disk, no screen, and it must spend almost all its life asleep or its batteries last days instead of months. Practical 1 takes one apart.

TinyOS is an operating system written for motes. It has no processes and no threads in the ordinary sense. An application is a set of small software components that call each other and react to events: a timer firing, a packet arriving, a sensor reading being ready. Practical 3 explains its architecture.

nesC is the language TinyOS is written in, a dialect of C in which a program is made of components wired together. The nesC compiler turns the whole application into one ordinary C file, and a C compiler then turns that into the mote's program. Practical 4 teaches the language.

TOSSIM is TinyOS's simulator. The same nesC application is compiled a second way: instead of a program for the mote's microcontroller, the build produces a library for your PC in which the mote's hardware (its timers, its LEDs, its radio) is replaced by simulated versions, and a Python script creates as many motes as you want and runs them. Every exercise in Module 1 is done in TOSSIM, and it needs no hardware at all. TOSSIM simulates one particular mote, the MICAz.

Why Ubuntu 22.04 and not a newer one

TOSSIM's Python interface is written for Python 2, not Python 3. Ubuntu 22.04 is the last long-term release that still packages Python 2; Ubuntu 24.04 does not. Everything else this module needs (the nesC compiler, both mote cross-compilers, gcc-10) is packaged by 22.04 as well, so the whole laboratory installs from Ubuntu's own archive plus one download of TinyOS itself.

TinyOS 2.1.2 is the release this book uses. It is the release tagged in TinyOS's own repository on GitHub, dated 20 August 2012, and it is the one TinyOS's own tutorials were written for.

Step 1: the packages

Every package the module needs, in one command:

$ sudo apt update
$ sudo apt install nescc gcc-avr avr-libc binutils-avr gcc-msp430 msp430-libc binutils-msp430 msp430mcu python2 python2-dev g++ gcc-10 make git autoconf automake default-jdk-headless
munotes.in6

The TinyOS Laboratory from Zero: nesC, TinyOS 2.1.2 and TOSSIM on Ubuntu

PackageWhy this laboratory needs it
nesccthe nesC compiler
gcc-msp430, msp430-libc, binutils-msp430, msp430mcuthe C compiler for the TelosB mote's MSP430 microcontroller
gcc-avr, avr-libc, binutils-avrthe C compiler for the MICAz mote's AVR microcontroller
python2, python2-devTOSSIM's scripting language and the headers its Python module is built against
g++, makeTOSSIM's own C++ code, and the build system
gcc-10an older C compiler, for the first of the three problems in step 5
git, autoconf, automaketo fetch TinyOS and build its tools
default-jdk-headlessJava, for the TinyOS tools written in it (Practical 8 uses them)

Check that the important ones are there. The machine this book was run on answers:

$ grep PRETTY_NAME /etc/os-release
PRETTY_NAME="Ubuntu 22.04.5 LTS"
$ nescc --version | head -1
nescc: 1.3.5
$ python2 --version
Python 2.7.18
$ gcc-10 --version | head -1
gcc-10 (Ubuntu 10.5.0-1ubuntu1~22.04.3) 10.5.0
$ msp430-gcc --version | head -1
msp430-gcc (GCC) 4.6.3 20120301 (mspgcc LTS 20120406 unpatched)

Step 2: TinyOS 2.1.2 itself

TinyOS is not packaged by Ubuntu. It is fetched from its own repository at the release tag, into your home directory:

$ cd ~
$ git clone --branch release_tinyos_2_1_2 --depth 1 https://github.com/tinyos/tinyos-main.git tinyos-2.1.2

--branch release_tinyos_2_1_2 picks the release, and --depth 1 fetches that one version rather than the whole history. Check what arrived:

$ cd ~/tinyos-2.1.2
$ git --no-pager log -1 --format='%h %cd' --date=short
a2d4e21 2012-08-20
$ ls
README	apps  doc  licenses  release-notes.txt	support  tools	tos

Four of those folders matter.

FolderWhat is in it
tosTinyOS itself: its interfaces, its components, and the code for each chip and each mote platform
appsexample applications, Blink among them, each one a folder you can copy and build
supportthe make rules every build uses, and the Java and Python libraries that talk to motes
toolsthe programs that drive the nesC compiler, which step 3 builds

doc/txt also holds TinyOS's design documents, called TEPs (TinyOS Enhancement Proposals). They are the authority for how TinyOS behaves, and this book quotes them by number: TEP 106 on the scheduler, TEP 113 on serial communication, and others.

Step 3: the TinyOS tools

TinyOS does not call the nesC compiler directly. It calls ncc, a wrapper that knows where every TinyOS component lives and passes the right options for each mote, and it uses a few smaller programs beside it. They are built from the tools folder:

$ cd ~/tinyos-2.1.2/tools
$ ./Bootstrap
$ ./configure
$ make
$ sudo make install

They install into /usr/local/bin:

$ which ncc mig tos-ident-flags
/usr/local/bin/ncc
/usr/local/bin/mig
/usr/local/bin/tos-ident-flags

ncc compiles an application, mig generates message classes for programs on the PC (Practical 8), and tos-ident-flags stamps every build with its name and time.

munotes.in7

The TinyOS Laboratory from Zero: nesC, TinyOS 2.1.2 and TOSSIM on Ubuntu

Step 4: six environment variables

The build system finds TinyOS through environment variables. Add these six lines to the end of the file ~/.bashrc, so that every terminal you open has them:

$ tail -7 ~/.bashrc
# TinyOS 2.1.2
export TOSROOT=$HOME/tinyos-2.1.2
export TOSDIR=$TOSROOT/tos
export MAKERULES=$TOSROOT/support/make/Makerules
export CLASSPATH=$TOSROOT/support/sdk/java/tinyos.jar:.
export PYTHONPATH=$TOSROOT/support/sdk/python
export PYTHON_VERSION=2.7

Then close the terminal and open a new one, or type source ~/.bashrc. Check one:

$ echo $MAKERULES
/home/student/tinyos-2.1.2/support/make/Makerules
VariableWhat reads it
TOSROOTeverything: it is where TinyOS lives
TOSDIRncc, to find the operating system's components
MAKERULESevery application's Makefile, whose last line is include $(MAKERULES)
CLASSPATHJava, to find TinyOS's Java tools
PYTHONPATHPython, to find TinyOS's Python library
PYTHON_VERSIONthe simulator's build rules, to find Python 2's headers

The last one needs explaining, because TinyOS's instructions do not mention it. The simulator's make rules work out which Python to build against by running the command python --version. Ubuntu 22.04 has no command called python at all, only python2 and python3, so without this variable the rules ask a program that does not exist and the build fails looking for Python's headers. Setting PYTHON_VERSION=2.7 answers the question for them.

Step 5: the first build, and the three lines every Makefile needs

Never build inside $TOSROOT itself. Copy the application to your own folder and work there:

$ cp -r $TOSROOT/apps/Blink ~/Blink
$ cd ~/Blink
$ ls
BlinkAppC.nc  BlinkC.nc  Makefile  README.txt
$ cat Makefile
COMPONENT=BlinkAppC
include $(MAKERULES)

An application is a folder with a Makefile and its nesC files. COMPONENT names the top-level component, the one that wires the application together; Practical 4 explains what that means.

The command that builds for the simulator is make micaz sim: build for the MICAz platform, but as the simulator. On Ubuntu 22.04 it fails:

$ make micaz sim 2>&1 | grep -m1 error
/usr/include/stdio.h:294:51: error: ‘fclose’ undeclared here (not in a function)

Problem 1: glibc and a newer GCC

That error is not in Blink and not in TinyOS. It is in /usr/include/stdio.h, the C library's own header.

Here is what happens. From version 2.34, the GNU C library marks the function fopen with an attribute saying that whatever fopen returns must be released by fclose, and it does so only when the compiler is GCC 11 or newer. Ubuntu 22.04 has glibc 2.35 and GCC 11. nesC reads that header, rewrites all the declarations it needs into its one output file, and writes the fopen declaration, attribute and all, at a point where fclose has not been declared yet. The C compiler then rightly refuses it.

The fix is to have nesC use gcc-10, for which glibc leaves the attribute out. Where the compiler is chosen is not obvious: the MICAz simulation platform reads it from an environment variable called GCC, and TinyOS's own rules set that variable to plain gcc part of the way through the build. So the Makefile has to overrule them. Open Makefile in any editor and make it read:

munotes.in8

The TinyOS Laboratory from Zero: nesC, TinyOS 2.1.2 and TOSSIM on Ubuntu

COMPONENT=BlinkAppC
override GCC = gcc-10
export GCC
include $(MAKERULES)

override makes this setting win over any later one in TinyOS's rules, and export passes it on to the programs make runs. Build again:

$ make micaz sim 2>&1 | tail -1
*** Successfully built micaz TOSSIM library.

Problem 2: the library builds and will not load

The build now succeeds, and the simulator refuses to start. The smallest possible script, one that only loads TOSSIM, shows it:

from TOSSIM import *
print "TOSSIM loaded"
$ python2 load.py
Traceback (most recent call last):
  File "load.py", line 1, in <module>
    from TOSSIM import *
  File "/home/student/Blink/TOSSIM.py", line 7, in <module>
    import _TOSSIM
ImportError: /home/student/Blink/_TOSSIMmodule.so: undefined symbol: __nesc_atomic_end

__nesc_atomic_end is a function nesC writes into every application, declared inline. Under the rules of the C standard that compilers have followed by default since GCC 5, an inline function that is not also static gets no separate copy of its own, and the simulator is compiled without optimisation, so nothing inlines it either. The name is used and nowhere defined. The option -fgnu89-inline restores the older rule, under which the copy is made. It is passed to the nesC compiler through PFLAGS:

COMPONENT=BlinkAppC
override GCC = gcc-10
export GCC
PFLAGS += -fgnu89-inline
include $(MAKERULES)
$ make micaz sim 2>&1 | tail -1
*** Successfully built micaz TOSSIM library.
$ python2 load.py
TOSSIM loaded

Problem 3: a radio that never delivers anything, on some machines

The third line is for a problem that prints no error at all, which is what makes it worth knowing.

TOSSIM models radio noise, and it stores its noise readings in the C type char. A noise reading is a negative number of decibels, around minus 98. Whether a plain char can hold a negative number depends on the processor: on the Intel and AMD processors in most college laboratories it can, and on ARM processors (a Raspberry Pi, an Apple laptop running Ubuntu in a virtual machine) it cannot, so minus 98 is stored as 158. The simulated radio then hears a deafening signal on the channel forever, never finds it clear, and never sends a packet. Nothing reports a problem.

The laboratory this book was run on has an ARM processor, so the problem can be shown. RadioCountToLeds, another TinyOS example, has motes counting and broadcasting their count to each other. Copy it out:

munotes.in9

The TinyOS Laboratory from Zero: nesC, TinyOS 2.1.2 and TOSSIM on Ubuntu

$ cp -r $TOSROOT/apps/RadioCountToLeds ~/RadioCountToLeds
$ cd ~/RadioCountToLeds

Its Makefile also builds two message classes for programs on the PC, which this test does not need, so replace it with one carrying the first two fixes only:

COMPONENT=RadioCountToLedsAppC
override GCC = gcc-10
export GCC
PFLAGS += -fgnu89-inline
include $(MAKERULES)

And a script that makes two motes, puts a strong radio link between them in both directions, gives each the noise model TinyOS ships, boots them and runs three simulated seconds (Practical 7 explains every line of it):

from TOSSIM import *
import sys
t = Tossim([])
t.randomSeed(1)
r = t.radio()
r.add(1, 2, -60.0)
r.add(2, 1, -60.0)
noise = open(sys.argv[1]).readlines()[:100]
for n in (1, 2):
    m = t.getNode(n)
    for line in noise:
        m.addNoiseTraceReading(int(line))
    m.createNoiseModel()
    m.bootAtTime(n * 1000000)
t.addChannel("RadioCountToLedsC", sys.stdout)
while t.time() < 3 * t.ticksPerSecond():
    t.runNextEvent()
$ make micaz sim 2>&1 | tail -1
*** Successfully built micaz TOSSIM library.
$ python2 two.py $TOSDIR/lib/tossim/noise/meyer-heavy.txt > log1.txt
$ grep -c "packet sent" log1.txt
2
$ grep -c "Received" log1.txt
0

Each mote printed "packet sent" once and never again, and nothing was received. That line is printed when the radio accepts a packet, not when the packet goes out. The radio then waited for a clear channel that never came, so the send never completed, the application's lock on its one message buffer was never released, and every later timer found the buffer still busy and gave up. Now the third line:

COMPONENT=RadioCountToLedsAppC
override GCC = gcc-10
export GCC
PFLAGS += -fgnu89-inline -fsigned-char
include $(MAKERULES)
$ make micaz sim 2>&1 | tail -1
*** Successfully built micaz TOSSIM library.
$ python2 two.py $TOSDIR/lib/tossim/noise/meyer-heavy.txt > log2.txt
$ grep -c "packet sent" log2.txt
24
$ grep -c "Received" log2.txt
24
$ cd ~/Blink

Twenty-four packets sent and twenty-four received: each mote's timer fired twelve times in three seconds, and each heard all twelve of the other's. -fsigned-char makes char signed whatever the processor. On an Intel or AMD PC it changes nothing, because char is signed there already, and both runs deliver packets. Every Makefile in this book carries it, so every program here runs the same on either kind of machine.

These three lines go in every Makefile in this book:

COMPONENT=BlinkAppC
override GCC = gcc-10
export GCC
PFLAGS += -fgnu89-inline -fsigned-char
include $(MAKERULES)

Step 6: run Blink in TOSSIM

Blink is the smallest TinyOS application there is: three timers, each toggling one of the mote's three LEDs. Its code, without the licence text at the top of each file:

$ sed -n '/^module/,$p' BlinkC.nc
module BlinkC @safe()
{
  uses interface Timer<TMilli> as Timer0;
  uses interface Timer<TMilli> as Timer1;
  uses interface Timer<TMilli> as Timer2;
  uses interface Leds;
  uses interface Boot;
}
implementation
{
  event void Boot.booted()
  {
    call Timer0.startPeriodic( 250 );
    call Timer1.startPeriodic( 500 );
    call Timer2.startPeriodic( 1000 );
  }

  event void Timer0.fired()
  {
    dbg("BlinkC", "Timer 0 fired @ %s.\n", sim_time_string());
    call Leds.led0Toggle();
  }

  event void Timer1.fired()
  {
    dbg("BlinkC", "Timer 1 fired @ %s \n", sim_time_string());
    call Leds.led1Toggle();
  }

  event void Timer2.fired()
  {
    dbg("BlinkC", "Timer 2 fired @ %s.\n", sim_time_string());
    call Leds.led2Toggle();
  }
}
munotes.in10

The TinyOS Laboratory from Zero: nesC, TinyOS 2.1.2 and TOSSIM on Ubuntu

startPeriodic(250) starts a timer that fires every 250 of TinyOS's milliseconds, for ever, and each fired event toggles one LED. The dbg lines print only in the simulator; on a real mote they compile to nothing.

A TOSSIM simulation is driven by a Python script. This one creates one mote, boots it, says which messages it wants to see, and runs two simulated seconds:

from TOSSIM import *
import sys

t = Tossim([])
t.randomSeed(1)
t.addChannel("BlinkC", sys.stdout)
t.addChannel("LedsC", sys.stdout)

m = t.getNode(1)
m.bootAtTime(0)

while t.time() < 2 * t.ticksPerSecond():
    t.runNextEvent()
LineWhat it does
from TOSSIM import *loads the simulator the build just made
t = Tossim([])creates the simulation
t.randomSeed(1)fixes the simulator's random numbers, so every run prints exactly the same thing
t.addChannel("BlinkC", sys.stdout)prints every dbg message on the channel named BlinkC
m = t.getNode(1)mote number 1
m.bootAtTime(0)switches it on at time zero
t.runNextEvent()runs the next thing that is due, and moves the clock to it
t.ticksPerSecond()how many simulator ticks make one second

Build and run:

$ make micaz sim 2>&1 | tail -1
*** Successfully built micaz TOSSIM library.
$ python2 blink.py
DEBUG (1): Timer 0 fired @ 0:0:0.244140635.
DEBUG (1): LEDS: Led0 on.
DEBUG (1): Timer 0 fired @ 0:0:0.488281260.
DEBUG (1): LEDS: Led0 off.
DEBUG (1): Timer 1 fired @ 0:0:0.488281270
DEBUG (1): LEDS: Led1 on.
DEBUG (1): Timer 0 fired @ 0:0:0.732421885.
DEBUG (1): LEDS: Led0 on.
DEBUG (1): Timer 0 fired @ 0:0:0.976562510.
DEBUG (1): LEDS: Led0 off.
DEBUG (1): Timer 1 fired @ 0:0:0.976562520
DEBUG (1): LEDS: Led1 off.
DEBUG (1): Timer 2 fired @ 0:0:0.976562530.
DEBUG (1): LEDS: Led2 on.
DEBUG (1): Timer 0 fired @ 0:0:1.220703135.
DEBUG (1): LEDS: Led0 on.
DEBUG (1): Timer 0 fired @ 0:0:1.464843760.
DEBUG (1): LEDS: Led0 off.
DEBUG (1): Timer 1 fired @ 0:0:1.464843770
DEBUG (1): LEDS: Led1 on.
DEBUG (1): Timer 0 fired @ 0:0:1.708984385.
DEBUG (1): LEDS: Led0 on.
DEBUG (1): Timer 0 fired @ 0:0:1.953125010.
DEBUG (1): LEDS: Led0 off.
DEBUG (1): Timer 1 fired @ 0:0:1.953125020
DEBUG (1): LEDS: Led1 off.
DEBUG (1): Timer 2 fired @ 0:0:1.953125030.
DEBUG (1): LEDS: Led2 off.

Read it as a log of the mote's life. LED 0 toggles on every firing of Timer 0, LED 1 on every firing of Timer 1, LED 2 on every firing of Timer 2, and Timer 0 fires twice as often as Timer 1, which fires twice as often as Timer 2, exactly as the three startPeriodic calls say.

munotes.in11

The TinyOS Laboratory from Zero: nesC, TinyOS 2.1.2 and TOSSIM on Ubuntu

But Timer 0 first fires at 0.244 seconds, not 0.250. That is not an error. A TinyOS millisecond is not a thousandth of a second. TEP 102, the design document for TinyOS timers, says so in as many words: "one second contains 1024 binary milliseconds". So startPeriodic(250) means every 250/1024 of a second, which is 0.244140625 seconds, and every time in the log is a multiple of it: 0.244, 0.488, 0.732, 0.977.

The last few nanoseconds are the simulator's. TOSSIM runs each task a fixed 100 ticks, 10 nanoseconds, after it was posted (the constant is in its scheduler, tos/lib/tossim/SimSchedulerBasicP.nc). TinyOS's timer code, tos/lib/timer/VirtualizeTimerC.nc, fires one due timer and then posts a task to look for the next. So where two timers fall due together, the second fires one task later than the first: 0.488281260 and then 0.488281270. The mote does one thing at a time, even when two things are due at once, and Practical 3 is about exactly that.

Step 7: build the same application for a real mote

A real mote runs the same nesC files, compiled for its own microcontroller. For a TelosB, whose microcontroller is the MSP430:

$ make telosb 2>&1 | grep bytes
            2538 bytes in ROM
              56 bytes in RAM

Those two numbers are the compiler's own measurement of the program: what it takes of the mote's program memory (ROM) and of its working memory (RAM). A TelosB has 48 KB of the first and 10 KB of the second, which is why Practical 1 treats memory as a resource to be budgeted. The file the mote actually runs is build/telosb/main.ihex. With a TelosB plugged into a USB port, make telosb install,1 would build it, stamp it with node number 1 and write it to the mote; without the hardware that step cannot be shown here.

The MICAz, the mote TOSSIM simulates, is another matter:

$ make micaz 2>&1 | grep -m1 warning
/usr/lib/gcc/avr/5.4.0/../../../avr/include/avr/io.h:623:6: warning: #warning "device type not defined"

The build passes the compiler -mmcu=atmega128, the option naming the MICAz's chip, and Ubuntu's AVR compiler, version 5.4, still reports that no device type is defined: the chip's name does not reach it through nesC. Without it none of the chip's registers are defined, and the build fails with hundreds of errors after this first warning. Defining the chip by hand only reaches the next error, inside TinyOS's own code, which was written for older compilers than this one. Making TinyOS 2.1.2 build for the MICAz with this compiler means patching TinyOS, and this book does not.

munotes.in12

The TinyOS Laboratory from Zero: nesC, TinyOS 2.1.2 and TOSSIM on Ubuntu

It does not matter for this book: make micaz sim does not use the AVR compiler at all, and TOSSIM is where every Module 1 exercise is done. For a real-hardware build on this Ubuntu, use a TelosB.

Step 8: what the build left behind

$ ls
BlinkAppC.nc  Makefile	  TOSSIM.py   _TOSSIMmodule.so	blink.py  load.py
BlinkC.nc     README.txt  TOSSIM.pyc  app.xml		build	  simbuild
$ ls simbuild/micaz
app.c  c-support.o  ident_flags.txt  pytossim.o  sim.o	tossim.o
$ wc -l < simbuild/micaz/app.c
9499
FileWhat it is
simbuild/micaz/app.cthe whole application as ONE C file, written by the nesC compiler
_TOSSIMmodule.sothat C, compiled with the simulator, as a library Python can load
TOSSIM.pythe Python side of the same library; from TOSSIM import * reads it
app.xmla description of the application's components and variables, which Practical 6 uses to read a variable while the simulation runs
build/telosb/main.ihexthe real mote's program, from step 7

The size of app.c is worth a look. Blink is two short files, and the C the compiler wrote from them runs to thousands of lines: the timers, the LEDs, the scheduler and the rest of the operating system Blink uses, all put together into one program. That is how TinyOS works. There is no operating system on the mote apart from your application; the parts of TinyOS you use are compiled into it.

The shape of every Module 1 practical

Every TinyOS exercise in this book has the same four files and the same two commands:

FileWhat it holds
MakefileCOMPONENT=, the three lines from step 5, and include $(MAKERULES)
SomethingAppC.ncthe configuration: which components the application uses and how they are wired
SomethingC.ncthe module: the application's own code
run.pythe Python script that creates the motes and runs the simulation
$ make micaz sim
$ python2 run.py

Procedure

  1. Install the packages with apt.
  2. Clone TinyOS 2.1.2 at its release tag into the home directory.
  3. Build and install the TinyOS tools from its tools folder.
  4. Add the six environment variables to ~/.bashrc and open a new terminal.
  5. Copy Blink out of $TOSROOT/apps and build it with make micaz sim; meet the glibc error and fix it with gcc-10.
  6. Meet the undefined __nesc_atomic_end and fix it with -fgnu89-inline.
  7. Add -fsigned-char, which matters on ARM machines and changes nothing elsewhere.
  8. Run Blink for two simulated seconds with a Python script and read the log.
  9. Build Blink for a TelosB and read its ROM and RAM.

Observations

MeasuredValue
Ubuntu22.04.5 LTS
nesC1.3.5
Python for TOSSIM2.7.18
TinyOS2.1.2, commit a2d4e21, 20 August 2012
First make micaz simfails: fclose undeclared, in stdio.h
With gcc-10builds; Python cannot load it, __nesc_atomic_end undefined
With -fgnu89-inlinebuilds and runs
RadioCountToLeds without -fsigned-char, on ARM2 packets sent, 0 received
RadioCountToLeds with it, 3 simulated seconds24 packets sent, 24 received
Blink, two simulated secondsTimer 0 every 0.244 s (250/1024), Timer 1 every 0.488 s, Timer 2 every 0.977 s
Blink for a TelosB2538 bytes of ROM, 56 bytes of RAM
munotes.in13

The TinyOS Laboratory from Zero: nesC, TinyOS 2.1.2 and TOSSIM on Ubuntu

Result

The nesC compiler, TinyOS 2.1.2 and TOSSIM were installed on Ubuntu 22.04 from the Ubuntu archive and TinyOS's own repository, with six environment variables. TinyOS's Blink application was built for the simulator after three lines were added to its Makefile: gcc-10 for the C library's newer header, -fgnu89-inline for nesC's inline functions, and -fsigned-char for the simulator's radio noise on ARM processors. Blink ran in TOSSIM and toggled its three LEDs every 250, 500 and 1000 of TinyOS's binary milliseconds, which are 1/1024 of a second each, so every 0.244, 0.488 and 0.977 seconds; and the same application compiled for a real TelosB mote to 2538 bytes of ROM and 56 bytes of RAM.

Where marks are lost

Building inside $TOSROOT/apps. Copy the application out first. A build inside TinyOS's own tree leaves files there, and your next copy of the example carries them.

Forgetting source ~/.bashrc. A terminal opened before the six lines were added does not have them, and the build fails with MAKERULES empty.

Typing python run.py. There is no python on Ubuntu 22.04, and python3 cannot load TOSSIM. It is python2.

A Makefile without the three lines. On this Ubuntu the build fails, or builds and will not load. Write them into every Makefile in the journal and in the examination.

Reading the ROM and RAM as the mote's capacity. They are what your program uses. The mote's capacity is in its datasheet.

Not seeding the simulator. Without t.randomSeed(1) two runs of the same script can print different logs, and the journal no longer matches a run.

For the journal

This chapter is the laboratory setup. If your college asks for it as a practical, write: aim; what TinyOS, nesC and TOSSIM are, in three lines each; the packages and why each is needed; the clone and the tools build; the six environment variables and what reads each; the three Makefile lines and the problem each fixes; the Blink script with its output; the TelosB build with its ROM and RAM; the observation table; the result.

Quick revision

  • TinyOS is an operating system for motes; nesC is its language; TOSSIM is its simulator.
  • An application is compiled into ONE C file with the parts of TinyOS it uses.
  • Ubuntu 22.04, because TOSSIM needs Python 2, and 22.04 is the last LTS that has it.
  • git clone --branch release_tinyos_2_1_2 --depth 1 for TinyOS; the tools from tools/.
  • Six variables: TOSROOT, TOSDIR, MAKERULES, CLASSPATH, PYTHONPATH, PYTHON_VERSION.
  • Three Makefile lines: override GCC = gcc-10, export GCC, PFLAGS += -fgnu89-inline -fsigned-char.
  • make micaz sim builds for TOSSIM; python2 run.py runs it.
  • t.randomSeed(1) makes every run print the same log.
  • A TinyOS millisecond is 1/1024 of a second (TEP 102): startPeriodic(250) fires every 0.244 s.
  • make telosb builds a real mote image on this Ubuntu; make micaz does not.
munotes.in14

The TinyOS Laboratory from Zero: nesC, TinyOS 2.1.2 and TOSSIM on Ubuntu

Questions you must be able to answer

1. What is the difference between TinyOS, nesC and TOSSIM? TinyOS is the operating system, nesC is the language it and its applications are written in, and TOSSIM is the simulator that runs a TinyOS application on a PC instead of a mote.

2. What does make micaz sim produce, and how does it differ from make telosb? make micaz sim produces a library for the PC, _TOSSIMmodule.so with TOSSIM.py, in which the MICAz's hardware is simulated and which a Python script drives. make telosb produces a program for a real TelosB's microcontroller, main.ihex, which is written to the mote.

3. Why Python 2? TOSSIM's Python interface was written against Python 2's C interface, and TinyOS 2.1.2 predates any Python 3 version of it.

4. What does PYTHON_VERSION=2.7 fix? The simulator's build rules find Python's headers by running python --version, and Ubuntu 22.04 has no python command. The variable answers the question directly.

5. The build fails with "fclose undeclared" in stdio.h. Why, and what is the fix? glibc 2.34 and later attach an attribute naming fclose to fopen when the compiler is GCC 11 or newer, and nesC writes that declaration where fclose is not yet declared. Building with gcc-10, through override GCC = gcc-10 and export GCC in the Makefile, makes glibc leave the attribute out.

6. The build succeeds, but Python says __nesc_atomic_end is undefined. Why? nesC declares it inline without static, and under the C99 rules GCC has used by default since version 5, that produces no separate copy of the function. -fgnu89-inline restores the older rule.

7. What does -fsigned-char do, and why does it matter only on some machines? It makes char signed. TOSSIM stores radio noise readings, which are negative, in char; on ARM a plain char is unsigned, the readings become large positive numbers, and the radio never finds the channel clear. On Intel and AMD processors char is already signed.

8. What is t.randomSeed(1) for? TOSSIM uses random numbers for radio timing. Fixing the seed makes two runs of the same script print exactly the same log, which is what lets a journal's output be checked against a run.

munotes.in15

The TinyOS Laboratory from Zero: nesC, TinyOS 2.1.2 and TOSSIM on Ubuntu

9. Blink starts Timer 0 with startPeriodic(250). Why does the log show it firing at 0.244 seconds? Because TinyOS timers count in binary milliseconds, 1024 to a second (TEP 102), so 250 of them are 250/1024 of a second, 0.244140625 seconds. The last few nanoseconds are TOSSIM's task latency of 10 nanoseconds.

10. How often does LED 0 actually blink? A toggle turns it on at one firing and off at the next, so one complete on-and-off takes two periods, 500/1024 of a second, about 0.49 seconds: a little over two blinks a second.

11. Why is there no operating system on the mote apart from your program? Because nesC compiles the whole application, together with every part of TinyOS it uses, into one program. The mote runs that program and nothing else.

Contents This chapter on its own page

munotes.in16

Chapter Three

Practical 1: The Sensor Node Hardware Architecture

Syllabus topic Module 1, "Study of Sensor Node Hardware Architecture: Design a conceptual block diagram of a wireless sensor node and analyse the role of sensing unit, microcontroller, transceiver, memory, and power supply in energy- constrained environments."

Aim

To design a conceptual block diagram of a wireless sensor node, and to analyse the role of its sensing unit, microcontroller, transceiver, memory and power supply in an energy-constrained environment, using the figures of two real sensor nodes.

What you need to know before you start

A wireless sensor node, or mote, is a small computer built to be placed somewhere and left: in a field, on a bridge, inside a machine, on a patient. It measures something, decides what to do with the measurement, sends it over a radio, and does that for months on a pair of batteries that nobody is going to come and change.

That last requirement shapes everything else. A laptop is designed to be fast and is plugged in every night. A sensor node is designed to last, and every one of its parts is chosen and used with one question in mind: how little energy can this take?

A sensor node has five units, and MU names every one of them:

  1. The sensing unit turns something physical (temperature, light, vibration, humidity) into a number.
  2. The microcontroller is the processor. It reads the sensing unit, decides what to send and when, runs the network protocols and, above all, decides when everything else may sleep.
  3. The memory: working memory for the program's variables, program memory for the program itself, and often a separate flash chip for storing readings.
  4. The transceiver is the radio, which both transmits and receives.
  5. The power supply: usually two AA cells and a regulator that keeps the voltage steady as the cells run down.

Energy is power multiplied by time, and a battery's capacity is quoted in milliampere-hours (mAh): a 3,000 mAh cell can supply 3,000 mA for one hour, or 1 mA for 3,000 hours. So the life of a node depends on its average current, and the average depends on how much of the time each unit spends in each of its modes. That is the whole of the analysis this practical asks for.

Step 1: the block diagram

The five units of a wireless sensor node, with the MICAz's and TelosB's parts named. Solid arrows carry data, dashed lines carry power.

Figure 3.1 Block diagram of a wireless sensor node

Read the diagram along its arrows.

Sensing unit to processing unit. A sensor produces a voltage that varies with what it measures. Signal conditioning amplifies and filters it into the range the converter expects. The ADC, analogue-to-digital converter, turns that voltage into a number the microcontroller can read. The MICAz's ADC is 10-bit, so a reading is a number from 0 to 1023; the TelosB's is 12-bit, 0 to 4095.

Processing unit. The microcontroller runs the application. Its on-chip memory holds the program (flash, which keeps its contents without power), the variables (RAM, which does not), and a few settings (EEPROM). An external flash chip holds readings when there are more than RAM can keep or when they must survive a restart.

munotes.in17

Practical 1: The Sensor Node Hardware Architecture

Processing unit to transceiver, both ways. The microcontroller hands the radio a packet to send and takes from it the packets it received. The transceiver here is the CC2420, a single chip that implements the radio layer of IEEE 802.15.4, the standard low-rate wireless network used by sensor networks: 2.4 GHz, 250 kilobits a second.

LEDs and a serial port let the node show its state and talk to a PC over a cable. A node plugged into a PC this way is how a base station is made (Practical 2 and Practical 8).

The power unit feeds everything, which is why its dashed lines reach every block. It is the only unit that does no work of its own and the one that decides how long all the others can work.

Step 2: two real sensor nodes

Two motes are used throughout this book. TOSSIM simulates the MICAz, and a real-hardware TinyOS build on Ubuntu 22.04 works for the TelosB (the TinyOS laboratory chapter showed both). They share their radio and differ in their microcontroller.

UnitMICAzTelosB
MicrocontrollerAtmel ATmega128L, about 8 MHzTI MSP430F1611
Program flash128 KB48 KB
RAM4 KB10 KB
EEPROM4 KBnone; 256 bytes of information flash
External flash512 KBnot in these datasheets
ADC10-bit, 8 channels12-bit
RadioCC2420, 2.4 GHz, 250 kbpsthe same CC2420
Supplytwo AA cells; the ATmega128L runs from 2.7 V to 5.5 Vthe MSP430 runs from 1.8 V to 3.6 V
Outdoor range75 m to 100 m (datasheet, line of sight)
Indoor range20 m to 30 m (datasheet)

The sources are the MICAz datasheet for the MICAz column's board figures, the ATmega128L and MSP430F1611 datasheets for each chip's memory and supply range, and TinyOS's own tos/platforms/micaz/hardware.h, whose comment gives the MICAz's clock as "~8MHz".

Four kilobytes of RAM is the number to remember. Every variable, every packet buffer, every queue and the program's stack must fit into 4,096 bytes on a MICAz. That is why TinyOS has no threads, each of which would need a stack of its own, and why Practical 3's scheduler exists.

Step 3: what each unit draws

A unit's current depends on its mode, and every chip here has at least two: working, and asleep.

UnitModeCurrentSource
Processor (MICAz board)active8 mAMICAz datasheet
Processor (MICAz board)sleepunder 15 microamperesMICAz datasheet
Radio (MICAz board)receive19.7 mAMICAz datasheet
Radio (MICAz board)transmit at 0 dBm17.4 mAMICAz datasheet
Radio (MICAz board)transmit at minus 10 dBm11 mAMICAz datasheet
Radio (MICAz board)sleep, regulator off1 microampereMICAz datasheet
CC2420 chipreceive18.8 mACC2420 datasheet
CC2420 chipidle (oscillator and regulator on)426 microamperesCC2420 datasheet
CC2420 chippower down (regulator on)20 microamperesCC2420 datasheet
ATmega128Lactive, 4 MHz, 3 V5 mA typicalATmega128L datasheet
ATmega128Lpower-down, watchdog onunder 15 microamperesATmega128L datasheet
MSP430F1611active, 1 MHz, 2.2 V330 microamperesMSP430F1611 datasheet
MSP430F1611standby1.1 microamperesMSP430F1611 datasheet
munotes.in18

Practical 1: The Sensor Node Hardware Architecture

Three things in that table matter more than the rest.

Receiving costs more than transmitting. 19.7 mA against 17.4 mA on the MICAz. And a radio that is listening for a packet that may never come is receiving: it draws the full receive current whether or not anything arrives. This is called idle listening, and step 4 shows it is what empties a sensor node's battery.

Asleep is a thousand times cheaper than awake. The processor falls from 8 mA to under 15 microamperes, the radio from 19.7 mA to 1 microampere.

Watch the word "idle". The MICAz datasheet lists the radio's "Idle mode, voltage regular on" at 20 microamperes. The CC2420's own datasheet calls that same state power down and uses "idle" for a different one, with the crystal oscillator running, at 426 microamperes. Two datasheets, one chip, one word meaning two things. Always read the condition column, not just the mode's name.

Step 4: the energy budget, computed

The average current of a node that is awake a fraction d of the time, and asleep the rest, is:

Average current = d × (current awake) + (1 - d) × (current asleep)

and its life on a battery of capacity C is C divided by the average current. The program below does exactly that, for the MICAz, with every figure taken from the tables above. Save it as energy.py:

# energy.py: the energy budget of a MICAz mote, from its datasheets.
# Currents are in milliamperes, and are the MICAz datasheet's own figures.

VOLTS = 3.0             # two AA cells in series
CAPACITY_MAH = 3000.0   # an AA alkaline cell at a 25 mA drain (Energizer E91)

CPU_ACTIVE = 8.0        # processor, active
CPU_SLEEP = 0.015       # processor, sleep ("< 15 uA")
RADIO_RX = 19.7         # radio receiving, which is also what listening costs
RADIO_TX = 17.4         # radio transmitting at 0 dBm
RADIO_SLEEP = 0.001     # radio asleep, voltage regulator off

awake = CPU_ACTIVE + RADIO_RX
asleep = CPU_SLEEP + RADIO_SLEEP
print("Awake, radio listening: %7.3f mA" % awake)
print("Everything asleep:      %7.3f mA" % asleep)
print()
print("Awake for   Average current   Spent asleep      Battery life")
for duty in (1.0, 0.1, 0.01, 0.001):
    average = duty * awake + (1 - duty) * asleep
    share = (1 - duty) * asleep / average * 100
    hours = CAPACITY_MAH / average
    print("%7.1f %%   %12.4f mA   %9.1f %%   %7.0f hours, %5.0f days"
          % (duty * 100, average, share, hours, hours / 24))

# One IEEE 802.15.4 frame as the CC2420 sends it (CC2420 datasheet, section 16):
# 4 preamble bytes, 1 start-of-frame byte and 1 length byte, then TinyOS's
# 11-byte header (tos/chips/cc2420/CC2420.h), the payload and a 2-byte checksum.
RATE = 250000           # bits per second
print()
for payload in (2, 28):
    frame = 4 + 1 + 1 + 11 + payload + 2
    seconds = frame * 8 / RATE
    microjoules = VOLTS * RADIO_TX * seconds * 1000   # V x mA x s = mJ
    cpu_ms = microjoules / (VOLTS * CPU_ACTIVE)       # uJ / mW = ms
    print("Payload %2d bytes: %2d bytes on air, %.3f ms, %4.1f uJ,"
          " as much as %.2f ms of processing, %5.0f cycles at 8 MHz"
          % (payload, frame, seconds * 1000, microjoules, cpu_ms, cpu_ms * 8000))
listening = VOLTS * RADIO_RX * 1.0 * 1000          # one second, in uJ
print("One second of listening: %.0f uJ, the energy of %.0f full packets"
      % (listening, listening / microjoules))
munotes.in19

Practical 1: The Sensor Node Hardware Architecture

$ python3 energy.py
Awake, radio listening:  27.700 mA
Everything asleep:        0.016 mA

Awake for   Average current   Spent asleep      Battery life
  100.0 %        27.7000 mA         0.0 %       108 hours,     5 days
   10.0 %         2.7844 mA         0.5 %      1077 hours,    45 days
    1.0 %         0.2928 mA         5.4 %     10245 hours,   427 days
    0.1 %         0.0437 mA        36.6 %     68675 hours,  2861 days

Payload  2 bytes: 21 bytes on air, 0.672 ms, 35.1 uJ, as much as 1.46 ms of processing, 11693 cycles at 8 MHz
Payload 28 bytes: 47 bytes on air, 1.504 ms, 78.5 uJ, as much as 3.27 ms of processing, 26170 cycles at 8 MHz
One second of listening: 59100 uJ, the energy of 753 full packets

Read the first table from the top.

Awake all the time, the node lasts about five days. 27.7 mA is the processor and the listening radio together, and a 3,000 mAh battery at 27.7 mA gives about 108 hours. That is what a sensor node with no power management would do, and it is useless: nobody deploys a network that must be visited every five days.

Awake 1 per cent of the time, it lasts over a year. The average falls from 27.7 mA to under 0.3 mA, and the life rises nearly a hundredfold, because nearly all the energy was being spent awake. This is what duty cycling means: the node sleeps, wakes briefly to sense and to talk, and sleeps again. The duty cycle is the fraction of the time it is awake.

munotes.in20

Practical 1: The Sensor Node Hardware Architecture

But at 0.1 per cent the gain is not tenfold. Cutting the time awake by ten again only raises the life from 427 days to 2,861, about 6.7 times. The third column says why: at 0.1 per cent, 36.6 per cent of the average current is being spent asleep. Once a node is asleep nearly all the time, its sleep current, which was negligible at high duty cycles, becomes a large part of the budget, and only a better sleep current (the MSP430's 1.1 microampere standby against the ATmega128L's 15) can push the life further.

Treat the lives as upper limits, and compare them with each other rather than believing them one by one. The battery's 3,000 mAh is measured down to 0.8 V a cell, and the ATmega128L stops working below 2.7 V, which is 1.35 V a cell for two cells in series, so a real MICAz uses less of the battery than the program assumes. The shape of the result does not change: every factor of ten in the duty cycle is worth nearly a factor of ten in life, until the sleep current takes over.

Step 5: one packet against one second of listening

The second part of the output compares what the radio costs with what the processor costs.

A frame is what actually goes on the air, and it is longer than the data in it. The CC2420 datasheet gives the IEEE 802.15.4 frame as a 4-byte preamble, a 1-byte start-of-frame delimiter and a 1-byte length, then the frame itself; TinyOS's header for the CC2420 (tos/chips/cc2420/CC2420.h) adds 11 bytes before the data, and a 2-byte checksum follows it. So a packet carrying 2 bytes of data puts 21 bytes on the air, and one carrying TinyOS's largest default payload, 28 bytes, puts 47.

At 250 kilobits a second a 21-byte frame takes 0.672 milliseconds to send. At 3 V and 17.4 mA that is 35.1 microjoules, the same energy as the processor spends in 1.46 milliseconds of work, which the program counts as 11,693 clock cycles at 8 MHz. Sending a short packet costs as much energy as thousands of instructions, which is why a node should process data locally and send less of it. Practical 2 does exactly that with data aggregation.

And then the last line. One second of listening costs 59,100 microjoules, the energy of 753 full-size packets. A radio left on to wait for messages burns in one second what the node would spend sending its data for weeks. That single comparison is why sensor network MAC protocols, S-MAC in Practical 17 among them, exist at all: their whole purpose is to keep the radio off without missing the packets that matter.

munotes.in21

Practical 1: The Sensor Node Hardware Architecture

Step 6: memory, measured by the compiler

The processor's other constraint is memory, and here the compiler measures it for you. The awk script below reads the two lines make telosb prints and turns each into a share of the TelosB's 48 KB of flash and 10 KB of RAM:

/bytes in ROM/ { printf "ROM %6d of 49152 bytes, %4.1f per cent\n", $1, $1 * 100 / 49152 }
/bytes in RAM/ { printf "RAM %6d of 10240 bytes, %4.1f per cent\n", $1, $1 * 100 / 10240 }

Two applications, the smallest and one with a radio. Each is copied out of TinyOS and given the book's Makefile:

COMPONENT=BlinkAppC
override GCC = gcc-10
export GCC
PFLAGS += -fgnu89-inline -fsigned-char
include $(MAKERULES)
COMPONENT=RadioCountToLedsAppC
override GCC = gcc-10
export GCC
PFLAGS += -fgnu89-inline -fsigned-char
include $(MAKERULES)
$ cp -r $TOSROOT/apps/Blink ~/Blink && cp Makefile.blink ~/Blink/Makefile
$ cp -r $TOSROOT/apps/RadioCountToLeds ~/Radio && cp Makefile.radio ~/Radio/Makefile
$ cd ~/Blink && make telosb 2>&1 | awk -f ~/memory.awk
ROM   2538 of 49152 bytes,  5.2 per cent
RAM     56 of 10240 bytes,  0.5 per cent
$ cd ~/Radio && make telosb 2>&1 | awk -f ~/memory.awk
ROM  11346 of 49152 bytes, 23.1 per cent
RAM    356 of 10240 bytes,  3.5 per cent

The numbers are the compiler's own, and they say three things.

RAM is the scarce one. Blink needs 56 bytes of RAM, RadioCountToLeds 356, which includes a buffer for one packet and the radio stack's own state. On a TelosB's 10 KB that is little; on a MICAz's 4 KB it would already be nearly a tenth, and a real application with routing tables and queues fills it quickly.

The radio stack is most of a radio application. RadioCountToLeds is not much more code than Blink, yet its ROM is 11,346 bytes against 2,538. The difference is the CC2420 driver and the active-message layer compiled into it: in TinyOS the operating system you use is compiled into your application.

Memory costs energy too. RAM keeps its contents only while powered, so a sleeping node keeps its RAM supplied, which is part of its sleep current, and data that must survive a restart has to be written to flash, which takes time and energy of its own.

Step 7: the role of each unit in an energy-constrained environment

This is the analysis MU asks for, one unit at a time, with the figures from the steps above.

UnitIts roleIts largest energy costHow the cost is cut
Sensing unitturns a physical quantity into a numbera sensor or ADC left powered between readingspower the sensor only while sampling; sample no faster than the data changes
Microcontrollerruns the application and decides when everything else sleepsstaying active; 8 mA against under 15 microamperes asleepsleep between events; an event-driven operating system such as TinyOS sleeps whenever it has nothing to do
Memoryholds the program, its variables and stored readingskeeping RAM powered; writing to flashkeep buffers and tables small, and write to flash only what must survive
Transceiversends and receives packetslistening, at 19.7 mA, more than transmittingswitch the radio off whenever possible (duty cycling); send fewer, fuller packets; lower the transmit power when the receiver is near
Power supplyfeeds every other unitits own limits: capacity, and the voltage below which the node stopsa regulator; a microcontroller that runs at a lower voltage (the MSP430 runs down to 1.8 V)
munotes.in22

Practical 1: The Sensor Node Hardware Architecture

The table has a pattern. Every unit's energy is decided by how long it stays on, not by how hard it works while on. That is why the microcontroller's most important job is not computing but switching things off, and why the next nine practicals are about an operating system built around sleeping until an event arrives.

Procedure

  1. Draw the block diagram of a sensor node with its five units and label every arrow as data or power.
  2. Tabulate the parts of two real nodes, the MICAz and the TelosB, from their datasheets.
  3. Tabulate the current each unit draws in each mode, with the source of every figure.
  4. Write energy.py and run it: the average current and battery life at four duty cycles, the energy of one packet, and the energy of one second of listening.
  5. Build two TinyOS applications for a TelosB and measure their memory against the chip's capacity.
  6. For each unit, state its role, its largest energy cost and how that cost is reduced.

Observations

Measured or computedValue
MICAz awake, radio listening27.700 mA
MICAz asleep0.016 mA
Battery life at 100 per cent duty cycle108 hours, about 5 days
At 10 per cent1,077 hours, 45 days
At 1 per cent10,245 hours, 427 days
At 0.1 per cent68,675 hours, 2,861 days, with 36.6 per cent of the current spent asleep
A 2-byte payload21 bytes on the air, 0.672 ms, 35.1 microjoules
A 28-byte payload47 bytes on the air, 1.504 ms, 78.5 microjoules
One second of listening59,100 microjoules, as much as 753 full-size packets
Blink on a TelosB2,538 bytes of ROM (5.2 per cent), 56 bytes of RAM (0.5 per cent)
RadioCountToLeds on a TelosB11,346 bytes of ROM (23.1 per cent), 356 bytes of RAM (3.5 per cent)
munotes.in23

Practical 1: The Sensor Node Hardware Architecture

Result

A conceptual block diagram of a wireless sensor node was drawn with its sensing unit, processing unit (microcontroller and memory), transceiver and power unit, and made concrete with the parts of the MICAz and the TelosB taken from their datasheets. The energy analysis showed that a MICAz awake all the time lasts about 5 days on two AA cells, and about 427 days awake 1 per cent of the time; that at 0.1 per cent the sleep current becomes over a third of the budget; that one short packet costs as much energy as about 1.5 milliseconds of processing; and that one second of listening costs as much as 753 full-size packets. The compiler measured RAM as the scarcer memory. In an energy-constrained environment the role of every unit is decided by how long it is kept on, and the microcontroller's most important job is to keep the others asleep.

Where marks are lost

Four boxes and no arrows. A block diagram without its data and power connections is a list. Label what flows along each line.

Leaving out the power unit, or drawing it as one more box in the chain. It feeds every other unit, and the diagram must show that.

Quoting a battery life without a duty cycle. "The mote lasts a year" means nothing until you say how much of the time it is awake.

Believing transmitting is the expensive part. On the MICAz, receiving draws more than transmitting, and a radio that listens is receiving.

Mixing the datasheets' names for modes. The MICAz's "idle" is the CC2420's "power down". Quote the current with its condition.

Writing currents without units, or microamperes as milliamperes. 15 microamperes is 0.015 mA. A factor of a thousand wrong makes every conclusion wrong.

Taking the lives as exact. They are upper limits; say so, and why.

For the journal

Aim; the five units of a sensor node, each in two lines; the block diagram with its arrows labelled; the table of the two real nodes; the table of currents with their sources; the formula for the average current; energy.py with its output; the frame-length working for a 2-byte and a 28-byte payload; the memory measurements with memory.awk; the table of each unit's role, largest cost and how the cost is cut; the observation table; the result.

Quick revision

  • Five units: sensing unit, microcontroller, memory, transceiver, power supply.
  • MICAz: ATmega128L (128 KB flash, 4 KB RAM, 4 KB EEPROM), CC2420 radio, two AA cells.
  • TelosB: MSP430F1611 (48 KB flash, 10 KB RAM), the same CC2420.
  • CC2420: IEEE 802.15.4, 2.4 GHz, 250 kbps; receive 19.7 mA, transmit 17.4 mA at 0 dBm on the MICAz board.
  • Average current = d × awake + (1 - d) × asleep; life = capacity divided by average current.
  • MICAz: 5 days always awake, 427 days at 1 per cent.
  • At very low duty cycles the sleep current dominates.
  • One second of listening costs as much as 753 full-size packets: idle listening is the enemy.
  • Sending a short packet costs as much as about 1.5 ms of processing.
  • RAM, not flash, is the scarce memory.
munotes.in24

Practical 1: The Sensor Node Hardware Architecture

Questions you must be able to answer

1. Name the units of a wireless sensor node and what each does. The sensing unit turns a physical quantity into a number, the microcontroller runs the application and controls every other unit, the memory holds the program, its data and stored readings, the transceiver sends and receives packets, and the power supply feeds all of them.

2. Why is energy the central constraint of a sensor node? Nodes run on batteries and are left where they are placed, often in large numbers, so replacing batteries is impractical. The node's useful life is its battery life.

3. Which draws more on a MICAz, receiving or transmitting? Receiving: 19.7 mA against 17.4 mA at 0 dBm. Listening for a packet is receiving, so a radio left listening is the largest drain on a node.

4. What is a duty cycle, and what does it do to battery life? The fraction of the time a node is awake. On a MICAz, 100 per cent gives about 5 days and 1 per cent about 427 days on the same two AA cells.

5. Why does cutting the duty cycle from 1 per cent to 0.1 per cent not give ten times the life? Because the sleep current stays. At 0.1 per cent, 36.6 per cent of the average current is spent asleep, so the life rises only about 6.7 times.

6. How many bytes does a 2-byte payload put on the air, and why so many? 21: a 4-byte preamble, a start-of-frame byte and a length byte from IEEE 802.15.4, an 11-byte TinyOS header, the 2 bytes of data and a 2-byte checksum.

7. Why should a node process data before sending it? Because sending costs far more than computing: one short packet costs as much energy as about 1.5 milliseconds of processing, 11,693 clock cycles at 8 MHz.

8. The MICAz datasheet says the radio's idle mode draws 20 microamperes and the CC2420 datasheet says 426. Which is right? Both. The MICAz datasheet's "idle, voltage regulator on" is the state the CC2420 datasheet calls power down, 20 microamperes; the CC2420's own "idle" keeps the crystal oscillator running and draws 426.

9. Which memory is scarcer on a MICAz, and what follows from it? RAM, 4 KB against 128 KB of flash. It is why TinyOS has no threads, each of which would need its own stack, and why buffers and tables are kept small.

munotes.in25

Practical 1: The Sensor Node Hardware Architecture

10. What is the most important job of a sensor node's microcontroller? Deciding when every unit, itself included, may sleep. The node's life is set by how long things stay on.

Contents This chapter on its own page

munotes.in26

Chapter Four

Practical 2: Sensor Motes, a Base Station and Data Aggregation

Syllabus topic Module 1, "Sensor Mote and Base Station Communication Study: Develop a simple communication scenario between multiple sensor motes and a base station to study data aggregation and sink node operation."

Aim

To develop a communication scenario in which several sensor motes send readings to one base station, and to study data aggregation and the operation of the sink node.

What you need to know before you start

A base station is the node where a sensor network's data leaves the network. The MICAz datasheet puts it plainly: "A base station allows the aggregation of sensor network data onto a PC or other computer platform", and "Any MICAz Mote can function as a base station when it is connected to a standard PC interface or gateway board." In the network's own terms the base station is the sink: the node every reading flows towards.

Convergecast is the name for that traffic pattern: many sources, one destination. It is the commonest pattern in a sensor network and it has a problem built into it. Every reading must reach the same place, so the sink and the air around it are the busiest part of the network.

Data aggregation is combining readings into fewer, smaller results before they travel further. Instead of passing on four temperatures, the sink passes on one summary of them. The summaries that combine well are the ones every database has:

AggregateWhat it keepsWhat it answers
COUNThow many readings arriveddid everybody report?
MIN, MAXthe lowest and highest readingis anything out of range?
SUMthe totalneeded for the average
AVERAGESUM divided by COUNTthe typical value

Practical 1 showed why this matters. A packet carrying 2 bytes of data puts 21 on the air, and sending one costs as much energy as thousands of instructions. Computing a minimum is almost free; sending four packets instead of one is not.

The sink's work in this practical, which is what "sink node operation" means:

  1. Keep its radio on. A sink cannot sleep through the readings it exists to collect, which is why a real base station is usually on mains power or attached to a PC.
  2. Receive every reading addressed to it and ignore everything else.
  3. Group readings by the round, called an epoch, in which they were taken.
  4. At the end of each epoch, compute the aggregate, report it, and start again.

How a mote addresses the sink. TinyOS sends active messages. Every packet carries a destination address and a one-byte AM type that says what kind of message it is. Every mote has an address, TOS_NODE_ID, and in TOSSIM it is the mote's number in the Python script. This application sends to address 0, the sink, with AM type 6, and the sink only listens for type 6.

This practical gives the whole application and explains what each part does. Practicals 3 to 5 then teach the nesC model it is built on: components, commands, events, tasks and wiring.

munotes.in27

Practical 2: Sensor Motes, a Base Station and Data Aggregation

Step 1: the message

Every mote and the sink must agree on what a reading looks like on the air. Make a folder for the application:

$ mkdir ~/SenseToSink
$ cd ~/SenseToSink

and in it a header file, SenseToSink.h:

#ifndef SENSE_TO_SINK_H
#define SENSE_TO_SINK_H

enum {
  AM_READING = 6,      /* the message type the motes and the sink agree on */
  SINK = 0,            /* node 0 is the base station */
  EPOCH = 1024,        /* one reading a second: 1024 binary milliseconds */
  MOTES = 4,           /* how many sensor motes report to the sink */
  JITTER = 256         /* a mote waits a random 0 to 255 ms before sending */
};

typedef nx_struct reading_msg {
  nx_uint16_t epoch;   /* which round this reading belongs to */
  nx_uint16_t node;    /* who took it */
  nx_uint16_t value;   /* the reading, in tenths of a degree Celsius */
} reading_msg_t;

#endif

The message is three numbers, six bytes. nx_struct and nx_uint16_t are nesC's network types: they are laid out the same way on every platform, byte order included, so a MICAz, a TelosB and a PC all read the same bytes as the same numbers. A plain C struct gives no such promise, and a message is exactly the thing that crosses between machines.

EPOCH is 1024 because TinyOS counts 1024 milliseconds to the second (the TinyOS laboratory chapter showed where that is written). JITTER is explained in step 7.

Step 2: a sensor for the simulator

TOSSIM has no physical sensor. So the application's sensor is a small component of our own that answers the same interface a real sensor does, Read<uint16_t>. Save it as FakeTempC.nc:

/* A temperature sensor for the simulator, which has no real one.
   It answers the same Read interface a real sensor component does. */
module FakeTempC {
  provides interface Read<uint16_t>;
}
implementation {
  uint16_t count = 0;

  task void answer() {
    /* tenths of a degree: each mote reads a little differently */
    uint16_t value = 240 + 10 * TOS_NODE_ID + (count * 3 + TOS_NODE_ID) % 7;
    count++;
    signal Read.readDone(SUCCESS, value);
  }

  command error_t Read.read() {
    post answer();
    return SUCCESS;
  }
}

A reading is asked for with read() and delivered later, by the event readDone, carrying the value. That is how every sensor in TinyOS works, and it is called split-phase (Practical 5). The formula gives mote 1 readings around 25 degrees and each higher-numbered mote about one degree more, varying a little from epoch to epoch, so the aggregates have something to show. On a real mote the same application would wire Read to the sensor board's component instead, and nothing else would change.

munotes.in28

Practical 2: Sensor Motes, a Base Station and Data Aggregation

Step 3: one module, two roles

Every mote in a TOSSIM simulation runs the same program. So one module does both jobs, and chooses by its own address: node 0 behaves as the sink, every other node as a sensor mote. Save it as SenseToSinkC.nc:

#include "SenseToSink.h"

module SenseToSinkC {
  uses interface Boot;
  uses interface Timer<TMilli>;
  uses interface Timer<TMilli> as Jitter;
  uses interface Random;
  uses interface SplitControl as RadioControl;
  uses interface AMSend;
  uses interface Receive;
  uses interface Read<uint16_t>;
}
implementation {
  message_t packet;
  bool busy = FALSE;
  uint16_t epoch = 0;

  /* the sink's running aggregate for the epoch it is collecting */
  uint16_t heard = 0, low = 0xFFFF, high = 0;
  uint32_t sum = 0;

  event void Boot.booted() {
    call RadioControl.start();
  }

  event void RadioControl.startDone(error_t err) {
    if (TOS_NODE_ID == SINK)
      call Timer.startOneShot(EPOCH + EPOCH / 2);
    else
      call Timer.startPeriodic(EPOCH);
  }

  event void RadioControl.stopDone(error_t err) {}

  event void Timer.fired() {
    if (TOS_NODE_ID == SINK) {
      epoch++;
      if (heard > 0)
        dbg("Sink", "epoch %u: %u of %u motes, min %u, max %u, average %u\n",
            epoch, heard, MOTES, low, high, (uint16_t)(sum / heard));
      else
        dbg("Sink", "epoch %u: nothing heard\n", epoch);
      heard = 0; low = 0xFFFF; high = 0; sum = 0;
      call Timer.startOneShot(EPOCH);
    } else {
      epoch++;
      if (JITTER > 0)
        call Jitter.startOneShot(call Random.rand16() % JITTER);
      else
        call Read.read();
    }
  }

  event void Jitter.fired() {
    call Read.read();
  }

  event void Read.readDone(error_t err, uint16_t value) {
    reading_msg_t* m;
    if (busy) return;
    m = (reading_msg_t*) call AMSend.getPayload(&packet, sizeof(reading_msg_t));
    m->epoch = epoch;
    m->node = TOS_NODE_ID;
    m->value = value;
    if (call AMSend.send(SINK, &packet, sizeof(reading_msg_t)) == SUCCESS)
      busy = TRUE;
  }

  event void AMSend.sendDone(message_t* msg, error_t err) {
    busy = FALSE;
  }

  event message_t* Receive.receive(message_t* msg, void* payload, uint8_t len) {
    reading_msg_t* m = (reading_msg_t*) payload;
    if (TOS_NODE_ID != SINK || len != sizeof(reading_msg_t)) return msg;
    dbg("Raw", "epoch %u: reading %u from node %u\n", m->epoch, m->value, m->node);
    if (m->epoch == epoch + 1) {       /* belongs to the epoch being collected */
      heard++;
      sum += m->value;
      if (m->value < low) low = m->value;
      if (m->value > high) high = m->value;
    }
    return msg;
  }
}

Read it in the order things happen.

Boot. Every node switches its radio on. Starting the radio is split-phase too: RadioControl.start() returns at once and startDone arrives when the radio is ready.

A sensor mote's cycle. Its timer fires every epoch, one second. It counts the epoch, waits a random delay of up to a quarter of a second (step 7 explains why), asks the sensor for a reading, and when readDone delivers it, fills the three fields of the message and sends it to address SINK. The variable busy stops it touching its one message buffer while the radio still owns it; sendDone hands the buffer back.

munotes.in29

Practical 2: Sensor Motes, a Base Station and Data Aggregation

The sink's cycle. Its timer first fires one and a half epochs after boot and then every epoch, so it always fires half an epoch after the motes' readings for an epoch have been sent. Every reading it receives is printed on the Raw channel, and if the reading belongs to the epoch being collected it is added to four running figures: how many were heard, the sum, the lowest and the highest. When the timer fires, the sink prints the aggregate for that epoch on the Sink channel and starts the next one from zero.

Notice what the sink does not keep: the readings themselves. It needs four numbers per epoch however many motes report, which is what makes aggregation scale.

Step 4: the wiring and the Makefile

The configuration names the components the application uses and connects each interface the module uses to a component that provides it. Save it as SenseToSinkAppC.nc:

#include "SenseToSink.h"

configuration SenseToSinkAppC {}
implementation {
  components MainC, SenseToSinkC as App, FakeTempC;
  components new TimerMilliC();
  components new TimerMilliC() as JitterTimer, RandomC;
  components ActiveMessageC;
  components new AMSenderC(AM_READING);
  components new AMReceiverC(AM_READING);

  App.Boot -> MainC;
  App.Timer -> TimerMilliC;
  App.Jitter -> JitterTimer;
  App.Random -> RandomC;
  App.RadioControl -> ActiveMessageC;
  App.AMSend -> AMSenderC;
  App.Receive -> AMReceiverC;
  App.Read -> FakeTempC;
}

AMSenderC(AM_READING) and AMReceiverC(AM_READING) are where the AM type is fixed: this sender stamps every packet with type 6, and this receiver only passes up packets of type 6. Practical 4 explains the arrows.

The Makefile is the book's usual one:

COMPONENT=SenseToSinkAppC
override GCC = gcc-10
export GCC
PFLAGS += -fgnu89-inline -fsigned-char
include $(MAKERULES)

Step 5: the network, in the Python script

Five nodes: the sink, three motes close to it and to each other, and a fourth mote far from everyone. In TOSSIM a radio link is a gain in decibels, how much weaker the signal is when it arrives, set for each direction of each pair. Minus 60 is a strong link; minus 90 is close to the noise floor. Save the script as run.py:

from TOSSIM import *
import sys

t = Tossim([])
t.randomSeed(1)
r = t.radio()

# mote, and its gain to and from the sink (node 0)
for mote, gain in [(1, -60.0), (2, -62.0), (3, -64.0), (4, -90.0)]:
    r.add(mote, 0, gain)
    r.add(0, mote, gain)
# motes 1 to 3 hear each other well; mote 4 hears them only faintly
for a in (1, 2, 3):
    for b in (1, 2, 3):
        if a != b:
            r.add(a, b, -70.0)
    r.add(a, 4, -92.0)
    r.add(4, a, -92.0)

noise = open(sys.argv[1]).readlines()[:100]
for n in range(0, 5):
    m = t.getNode(n)
    for line in noise:
        m.addNoiseTraceReading(int(line))
    m.createNoiseModel()
    m.bootAtTime(1000 * n + 1)

for channel in sys.argv[2:]:
    t.addChannel(channel, sys.stdout)

while t.time() < 11 * t.ticksPerSecond():
    t.runNextEvent()
munotes.in30

Practical 2: Sensor Motes, a Base Station and Data Aggregation

The noise file is TinyOS's own recording of radio noise in a real building; each node is given its first hundred readings and builds a statistical model from them (Practical 7 explains the radio model in full). The nodes boot a tenth of a microsecond apart, and the simulation runs for eleven seconds, which is ten epochs. The last arguments name the channels to print, so the same script shows raw readings, aggregates, or both.

Step 6: build and run

$ make micaz sim 2>&1 | tail -1
*** Successfully built micaz TOSSIM library.
$ python2 run.py $TOSDIR/lib/tossim/noise/meyer-heavy.txt Raw Sink > both.txt
$ head -14 both.txt
DEBUG (0): epoch 1: reading 251 from node 1
DEBUG (0): epoch 1: reading 273 from node 3
DEBUG (0): epoch 1: reading 262 from node 2
DEBUG (0): epoch 1: 3 of 4 motes, min 251, max 273, average 262
DEBUG (0): epoch 2: reading 280 from node 4
DEBUG (0): epoch 2: reading 276 from node 3
DEBUG (0): epoch 2: reading 265 from node 2
DEBUG (0): epoch 2: reading 254 from node 1
DEBUG (0): epoch 2: 4 of 4 motes, min 254, max 280, average 268
DEBUG (0): epoch 3: reading 283 from node 4
DEBUG (0): epoch 3: reading 272 from node 3
DEBUG (0): epoch 3: reading 261 from node 2
DEBUG (0): epoch 3: reading 250 from node 1
DEBUG (0): epoch 3: 4 of 4 motes, min 250, max 283, average 266

Every line says DEBUG (0) because it comes from node 0, the sink: only the sink prints on these two channels. Within an epoch the raw readings arrive in whatever order the random delays gave them (in epoch 1, node 3 before node 2), and half an epoch later the aggregate line closes the epoch.

Epoch 1 is already a lesson. Only three readings arrived; node 4's is missing. The sink reports "3 of 4 motes", minimum 251, maximum 273, average 262. Node 4 is the warmest mote, around 28 degrees, so its absence pulls the maximum and the average down. Had the sink reported only the average, the PC would have believed the room was cooler than it was. An aggregate is only honest with its count beside it, which is why COUNT is always sent.

The aggregates alone, for all ten epochs:

$ python2 run.py $TOSDIR/lib/tossim/noise/meyer-heavy.txt Sink
DEBUG (0): epoch 1: 3 of 4 motes, min 251, max 273, average 262
DEBUG (0): epoch 2: 4 of 4 motes, min 254, max 280, average 268
DEBUG (0): epoch 3: 4 of 4 motes, min 250, max 283, average 266
DEBUG (0): epoch 4: 3 of 4 motes, min 264, max 286, average 275
DEBUG (0): epoch 5: 4 of 4 motes, min 256, max 282, average 267
DEBUG (0): epoch 6: 3 of 4 motes, min 252, max 274, average 263
DEBUG (0): epoch 7: 4 of 4 motes, min 255, max 281, average 268
DEBUG (0): epoch 8: 3 of 4 motes, min 251, max 273, average 262
DEBUG (0): epoch 9: 3 of 4 motes, min 254, max 276, average 265
DEBUG (0): epoch 10: 4 of 4 motes, min 250, max 283, average 266
munotes.in31

Practical 2: Sensor Motes, a Base Station and Data Aggregation

Step 7: what the sink received, and what it would send on

Count the raw readings the sink received from each mote, straight from the log:

$ python2 run.py $TOSDIR/lib/tossim/noise/meyer-heavy.txt Raw > raw.txt
$ wc -l < raw.txt
35
$ awk '{print "node", $NF}' raw.txt | sort | uniq -c
      9 node 1
     10 node 2
     10 node 3
      6 node 4

35 of the 40 readings reached the sink. Motes 2 and 3 were heard every time and mote 1 nine times in ten. Mote 4 was heard only six times. Its link to the sink is minus 90 dB, close to the noise the sink hears, so some of its packets arrive too damaged to decode; that is a property of the link, not of the application, and Practical 7 measures it. Mote 1's link is strong, minus 60 dB, so its one lost reading was lost to a collision, the only thing that defeats a link that strong: the random delays spread the four sends across a quarter of a second, but two delays can still fall close together.

If the sink passed every reading on to the PC or gateway, it would send one message per reading. With aggregation it sends one message per epoch, carrying five numbers: the epoch, the count, the minimum, the maximum and the average. This awk script compares the two from the log, using Practical 1's 19 bytes of frame overhead (preamble, start-of-frame byte, length byte, TinyOS's 11-byte header and the checksum). Save it as compare.awk:

# compare.awk: what the sink would send on, raw against aggregated.
# A raw message carries 6 bytes of data, an aggregate carries 10, and every
# frame adds 19 bytes of overhead on the air (Practical 1).
{ readings++; seen[$4] = 1 }
END {
    for (e in seen) epochs++
    printf "raw forwarding: %2d messages, %4d bytes on the air\n", readings, readings * (19 + 6)
    printf "aggregated:     %2d messages, %4d bytes on the air\n", epochs, epochs * (19 + 10)
}
munotes.in32

Practical 2: Sensor Motes, a Base Station and Data Aggregation

$ awk -f compare.awk raw.txt
raw forwarding: 35 messages,  875 bytes on the air
aggregated:     10 messages,  290 bytes on the air

Aggregation sent 10 messages instead of 35 and 290 bytes instead of 875, about a third. More important than the saving is what it depends on: the aggregated traffic does not grow with the number of motes. With forty motes, raw forwarding would send up to 400 messages in ten epochs and aggregation would still send 10. In a multi-hop network, where each message is relayed hop by hop towards the sink, that saving is made again at every hop, and the nodes nearest the sink, which relay everybody's traffic and are the first to run out of energy, gain the most.

What aggregation gives up is detail. The PC learns the minimum, the maximum and the average of each epoch, and no longer which mote read what. For a question like "is any part of the room too hot?" the maximum answers it; for "which corner is too hot?" it does not, and the sink must send the node number with the maximum as well. Choosing the aggregate is choosing which questions can still be answered.

Step 8: what happens when every mote speaks at once

Every mote's timer fires at the same moment each epoch, because they all booted together. Set the jitter to zero, so that every mote reads and sends the instant its timer fires, and run the same ten epochs again:

$ sed -i 's/JITTER = 256/JITTER = 0/' SenseToSink.h
$ grep 'JITTER =' SenseToSink.h
  JITTER = 0         /* a mote waits a random 0 to 255 ms before sending */
$ make micaz sim 2>&1 | tail -1
*** Successfully built micaz TOSSIM library.
$ python2 run.py $TOSDIR/lib/tossim/noise/meyer-heavy.txt Raw > raw0.txt
$ wc -l < raw0.txt
26
$ awk '{print "node", $NF}' raw0.txt | sort | uniq -c
      9 node 1
      7 node 2
      7 node 3
      3 node 4
$ python2 run.py $TOSDIR/lib/tossim/noise/meyer-heavy.txt Sink
DEBUG (0): epoch 1: 4 of 4 motes, min 251, max 284, average 267
DEBUG (0): epoch 2: 2 of 4 motes, min 254, max 276, average 265
DEBUG (0): epoch 3: 3 of 4 motes, min 250, max 272, average 261
DEBUG (0): epoch 4: 2 of 4 motes, min 253, max 264, average 258
DEBUG (0): epoch 5: 3 of 4 motes, min 256, max 271, average 262
DEBUG (0): epoch 6: 1 of 4 motes, min 252, max 252, average 252
DEBUG (0): epoch 7: 4 of 4 motes, min 255, max 281, average 268
DEBUG (0): epoch 8: 1 of 4 motes, min 262, max 262, average 262
DEBUG (0): epoch 9: 3 of 4 motes, min 254, max 276, average 265
DEBUG (0): epoch 10: 3 of 4 motes, min 250, max 283, average 268
munotes.in33

Practical 2: Sensor Motes, a Base Station and Data Aggregation

26 readings instead of 35, and the losses fall on the motes that were perfect before: motes 2 and 3 from ten to seven.

With no delay, all four motes want the air at the same instant. Motes 1, 2 and 3 can hear each other, so each checks the channel before sending, and TOSSIM's MAC waits a random backoff of 20 to 640 symbol periods first (tos/lib/tossim/sim_csma.h), under ten milliseconds, so that two motes starting together usually choose different moments. Usually is the word. Three motes drawing from a window of a few milliseconds sometimes draw close enough to collide, and each collision costs readings.

Mote 4 is worse off still. TOSSIM counts the channel as clear when what a mote hears is below minus 72 dBm (clearThreshold in tos/lib/tossim/CpmModelC.nc), and mote 4 reaches motes 1 to 3 at minus 92 dB. So neither side hears the other before sending: they are hidden terminals to each other, and the only place their packets meet is at the sink, where they collide. Practical 15 is about exactly that problem.

Two epochs, 6 and 8, delivered one reading of four, and an aggregate of one reading is barely an aggregate at all. Synchronised reporting is the enemy of convergecast. That is why the jitter is in the application, and why real sensor network protocols add random delays to anything that many nodes do at once.

Procedure

  1. Write the message format in a header file shared by every node.
  2. Write a sensor component for the simulator that answers the Read interface.
  3. Write one module whose node 0 acts as the sink and whose other nodes act as sensor motes.
  4. Wire it in a configuration, with a sender and a receiver of the same AM type.
  5. Describe the network in a Python script: five nodes, their link gains and a noise model.
  6. Build with make micaz sim and run ten epochs; read the raw readings and the aggregates.
  7. Count what the sink received from each mote and compare raw forwarding with aggregation.
  8. Set the jitter to zero, rebuild, run again and compare.

Observations

MeasuredWith jitter (0 to 255 ms)Without jitter
Readings taken by the four motes4040
Readings received by the sink3526
From mote 1 (link minus 60 dB)99
From mote 2 (minus 62 dB)107
From mote 3 (minus 64 dB)107
From mote 4 (minus 90 dB, hidden from the others)63
Epochs with all four readings52
Epochs with only one reading02
munotes.in34

Practical 2: Sensor Motes, a Base Station and Data Aggregation

Sent on by the sink, with jitterMessagesBytes on the air
Every reading forwarded35875
One aggregate per epoch10290

Result

A scenario of four sensor motes and one base station was built as a single nesC application whose node 0 acts as the sink, and run for ten one-second epochs in TOSSIM. The sink received each reading, grouped it by epoch and reported the count, minimum, maximum and average of every epoch. With a random delay of up to 255 milliseconds before each send, 35 of the 40 readings arrived; the far mote, on a weak link, lost four of its ten. Aggregation reduced what the sink would send on from 35 messages and 875 bytes to 10 messages and 290 bytes, and makes that traffic independent of the number of motes; the first epoch showed that an aggregate must carry its count, because a missing reading changes the average. With every mote sending at the same instant, only 26 readings arrived, because of collisions among the near motes and between them and the far mote, which is hidden from them.

Where marks are lost

Two programs, one for the mote and one for the base station. In TOSSIM every node runs the same image. One module with two roles, chosen by TOS_NODE_ID, is how it is done.

Reporting the average without the count. Epoch 1's average came out half a degree lower than all four readings would have given, because the warmest mote was missing. The count is what tells the reader.

Keeping every reading at the sink. The sink needs four running figures per epoch, not a list. That is what makes aggregation scale.

Forgetting the busy flag. A mote that fills its message buffer while the radio is still sending it corrupts the packet in flight.

Blaming the program for mote 4's losses. Its link is weak; measure the link before changing the code.

Every mote sending at the same instant. Synchronised timers make collisions. Add jitter.

Sending to the broadcast address. The readings are for the sink; address them to 0, so that only the sink passes them up.

For the journal

Aim; the base station and the sink, convergecast, and the aggregates COUNT, MIN, MAX, SUM and AVERAGE, briefly; the five files, SenseToSink.h, FakeTempC.nc, SenseToSinkC.nc, SenseToSinkAppC.nc and run.py, with the Makefile; the network drawn as five nodes with their link gains; the aggregate output for ten epochs; the raw counts per mote; the compare.awk output; the run without jitter; the observation tables; the result.

Quick revision

  • The base station, or sink, is where the network's data leaves it; every reading flows towards it (convergecast).
  • Data aggregation combines readings on the way: COUNT, MIN, MAX, SUM, AVERAGE.
  • The sink keeps four running figures per epoch, not the readings.
  • An aggregate must carry its count: a missing reading moves the average.
  • Aggregated traffic does not grow with the number of motes: 10 messages for 10 epochs, against 35 raw here.
  • The sink keeps its radio on; that is why base stations are mains powered or attached to a PC.
  • One program, two roles: if (TOS_NODE_ID == SINK).
  • nx_struct and nx_uint16_t give a message the same layout on every platform.
  • Synchronised sending collides: 26 readings without jitter, 35 with it.
  • A node below the -72 dBm clear threshold of the others is hidden from them.
munotes.in35

Practical 2: Sensor Motes, a Base Station and Data Aggregation

Questions you must be able to answer

1. What is the difference between a sensor mote and a base station? A sensor mote measures and sends. A base station, the sink, collects what the motes send and passes it out of the network to a PC or gateway. Any mote becomes a base station when it is connected to a PC.

2. What does data aggregation mean here, and what did it save? Combining the readings of an epoch into a count, minimum, maximum and average at the sink, so that one message leaves per epoch instead of one per reading: 10 messages and 290 bytes instead of 35 and 875.

3. Why must the count be sent with the average? Because a missing reading changes the average without saying so. In epoch 1 the warmest mote's reading was lost and the average came out lower than the room really was; "3 of 4 motes" is what tells the reader.

4. Why does one module do both jobs? Because every node in a TOSSIM simulation runs the same application. The module checks TOS_NODE_ID: node 0 behaves as the sink and every other node as a sensor mote.

5. What is JITTER for, and what happened without it? It makes each mote wait a random time before sending, so that motes whose timers fire together do not all transmit together. Without it the sink received 26 readings of 40 instead of 35.

6. Mote 4 lost readings even with jitter. Why? Its link to the sink is minus 90 dB, near the noise floor, so some packets arrive too damaged to decode; and at minus 92 dB it is hidden from the other motes, whose clear-channel threshold is minus 72 dBm, so its packets can also collide with theirs at the sink.

7. What does the AM type do? It labels what kind of message a packet carries. The sender stamps type 6 on every reading, and the sink's receiver passes up only type 6, so other traffic on the same radio is ignored.

munotes.in36

Practical 2: Sensor Motes, a Base Station and Data Aggregation

8. Why is the sink's radio never switched off? Because a reading can arrive at any moment and the sink exists to receive it. The energy-saving tricks of Practical 1 are for the motes; the sink is usually powered from the mains or a PC.

Contents This chapter on its own page

munotes.in37

Chapter Five

Practical 3: The TinyOS Architecture and Its Non-Preemptive Scheduler

Syllabus topic Module 1, "Understanding TinyOS Architecture and Execution Model: Examine the event-driven architecture of TinyOS and implement a simple application to demonstrate its non-preemptive scheduler."

Aim

To examine the event-driven architecture of TinyOS, and to implement a simple application that demonstrates its non-preemptive scheduler.

What you need to know before you start

Two ways to build an operating system

A desktop operating system is thread-based. Each program runs in threads, each thread has a stack of its own, and a scheduler interrupts whichever thread is running every few milliseconds to give another a turn. That interruption is called pre-emption, and it is why a slow program on a laptop does not freeze everything else.

A sensor node cannot afford that. A MICAz has 4 KB of RAM, and a thread needs a stack of its own, a few hundred bytes at the least; ten threads would use the whole memory before the application had declared a variable. So TinyOS is built the other way: it is event-driven. Nothing runs until something happens: a timer expires, a packet arrives, a sensor reading is ready. What happens is signalled as an event, the code that handles it runs, and when there is nothing left to do the processor goes to sleep. All of it runs on one stack.

The four things a TinyOS program is made of

Components. A TinyOS application is a set of components, each a small piece of software with a job: the timer system, the radio stack, the LEDs, your application. Practical 4 is about components and how they are joined.

Commands. A component asks another to do something by calling a command: call Timer0.startPeriodic(250), call Leds.led0Toggle(). A command runs straight away and returns.

Events. A component tells another that something has happened by signalling an event: Timer0.fired, Receive.receive. The component that wanted to know implements the event's handler. Commands go down towards the hardware; events come up from it.

Tasks. A task is a function whose running is put off until later. Posting it (post taskA();) puts it in a queue and returns immediately; the scheduler runs it when the processor is free. TEP 106, TinyOS's design document for tasks, calls a task "a form of deferred procedure call".

The scheduler's rules

TEP 106 states them, and every one is demonstrated in this practical:

  1. Tasks run to completion. "TinyOS tasks run to completion and do not pre-empt one another." Once a task starts, no other task runs until it returns.
  2. The basic scheduler is first in, first out. "The basic task in TinyOS 2.x is parameterless and FIFO." Tasks run in the order they were posted.
  3. A task can be waiting only once. "A basic post will only fail if and only if the task has already been posted and has not started execution." Each task has one place in the queue, one byte of state, and a second post while it waits is refused.
  4. Do not rely on the order. TEP 106 adds that "TinyOS components MUST NOT assume a FIFO policy": if B must follow A, A should post B.
munotes.in38

Practical 3: The TinyOS Architecture and Its Non-Preemptive Scheduler

This is what non-preemptive means. The scheduler never interrupts a task to run another; it waits for each to finish. So a task must be short, and a long computation is cut into pieces, each piece posting the next.

One kind of code can interrupt a task: a hardware interrupt handler. In nesC such code is marked async, and it can run in the middle of a task. That is why nesC has atomic blocks, and why an interrupt handler should do as little as possible and post a task for the rest. Everything in this practical is ordinary synchronous code, and TOSSIM, as step 6 explains, does not simulate interrupts pre-empting anything.

Step 1: TinyOS's own main loop

The whole execution model is a few lines of TinyOS's own code, and it is worth reading from the source rather than from a diagram. This is the main function of every TinyOS application, tos/system/RealMainP.nc:

$ sed -n '/int main() @C() @spontaneous()/,/^  }/p' $TOSDIR/system/RealMainP.nc
  int main() @C() @spontaneous() {
    atomic
      {
	/* First, initialize the Scheduler so components can post
	   tasks. Initialize all of the very hardware specific stuff, such
	   as CPU settings, counters, etc. After the hardware is ready,
	   initialize the requisite software components and start
	   execution.*/
	platform_bootstrap();

	call Scheduler.init();

	/* Initialize the platform. Then spin on the Scheduler, passing
	 * FALSE so it will not put the system to sleep if there are no
	 * more tasks; if no tasks remain, continue on to software
	 * initialization */
	call PlatformInit.init();
	while (call Scheduler.runNextTask());

	/* Initialize software components.Then spin on the Scheduler,
	 * passing FALSE so it will not put the system to sleep if there
	 * are no more tasks; if no tasks remain, the system has booted
	 * successfully.*/
	call SoftwareInit.init();
	while (call Scheduler.runNextTask());
      }

    /* Enable interrupts now that system is ready. */
    __nesc_enable_interrupt();

    signal Boot.booted();

    /* Spin in the Scheduler */
    call Scheduler.taskLoop();

    /* We should never reach this point, but some versions of
     * gcc don't realize that and issue a warning if we return
     * void from a non-void function. So include this. */
    return -1;
  }

It initialises the scheduler, initialises the hardware and then the software, running any tasks those initialisations post, switches interrupts on, signals Boot.booted to the application, and then hands the processor to the scheduler's task loop for ever. The task loop is in tos/system/SchedulerBasicP.nc:

munotes.in39

Practical 3: The TinyOS Architecture and Its Non-Preemptive Scheduler

$ sed -n '/command void Scheduler.taskLoop/,/^  }/p' $TOSDIR/system/SchedulerBasicP.nc
  command void Scheduler.taskLoop()
  {
    for (;;)
    {
      uint8_t nextTask;

      atomic
      {
	while ((nextTask = popTask()) == NO_TASK)
	{
	  call McuSleep.sleep();
	}
      }
      signal TaskBasic.runTask[nextTask]();
    }
  }

That is the entire event-driven architecture. Take the next task off the queue; if there is none, put the microcontroller to sleep (McuSleep.sleep()) until an interrupt wakes it; run the task to completion; repeat. An interrupt handler that wants work done posts a task, which wakes the loop.

And posting a task:

$ sed -n '/async command error_t TaskBasic.postTask/,/^  }/p' $TOSDIR/system/SchedulerBasicP.nc
  async command error_t TaskBasic.postTask[uint8_t id]()
  {
    atomic { return pushTask(id) ? SUCCESS : EBUSY; }
  }

pushTask puts the task's number at the tail of the queue unless it is already waiting there, and then the answer is EBUSY, TinyOS's error code for "busy, try again later". That is rule 3 in one line of code.

Step 2: an application built to make the scheduler visible

The application does three things, each at a different moment, and prints the simulated time of everything it does.

At boot it posts tasks A, B and C, then posts A a second time, and prints what the second post returned. Task A, when it runs, posts a fourth task, D.

At 100 binary milliseconds two timers fall due at the same instant. Timer 0's event posts a job of five pieces written as one task with a loop, and then Timer 1's event is delivered.

At 200 binary milliseconds the same again, except that the five pieces are written as one piece per task, each posting the next.

Make a folder for it:

$ mkdir ~/SchedulerDemo
$ cd ~/SchedulerDemo

The module, SchedulerDemoC.nc:

module SchedulerDemoC {
  uses interface Boot;
  uses interface Timer<TMilli> as Timer0;
  uses interface Timer<TMilli> as Timer1;
}
implementation {
  uint8_t phase = 0;
  uint8_t chunk = 0;

  task void taskA();
  task void taskB();
  task void taskC();
  task void taskD();
  task void wholeJob();
  task void oneChunk();

  event void Boot.booted() {
    error_t second;
    dbg("Sched", "%s booted: posting A, B and C\n", sim_time_string());
    post taskA();
    post taskB();
    post taskC();
    second = post taskA();
    dbg("Sched", "%s posting A again returns %u (%s)\n", sim_time_string(),
        second, second == SUCCESS ? "SUCCESS" : second == EBUSY ? "EBUSY" : "FAIL");
    call Timer0.startOneShot(100);
    call Timer1.startOneShot(100);
    dbg("Sched", "%s Boot.booted ends\n", sim_time_string());
  }

  task void taskA() {
    dbg("Sched", "%s task A starts, posts D\n", sim_time_string());
    post taskD();
    dbg("Sched", "%s task A ends\n", sim_time_string());
  }
  task void taskB() { dbg("Sched", "%s task B runs\n", sim_time_string()); }
  task void taskC() { dbg("Sched", "%s task C runs\n", sim_time_string()); }
  task void taskD() { dbg("Sched", "%s task D runs\n", sim_time_string()); }

  /* the same five pieces of work, done two ways */
  task void wholeJob() {
    uint8_t i;
    for (i = 1; i <= 5; i++)
      dbg("Sched", "%s   whole job: piece %u\n", sim_time_string(), i);
  }

  task void oneChunk() {
    chunk++;
    dbg("Sched", "%s   chunked job: piece %u\n", sim_time_string(), chunk);
    if (chunk < 5)
      post oneChunk();
  }

  event void Timer0.fired() {
    phase++;
    dbg("Sched", "%s Timer 0 fired (phase %u)\n", sim_time_string(), phase);
    if (phase == 1)
      post wholeJob();
    else
      post oneChunk();
  }

  event void Timer1.fired() {
    dbg("Sched", "%s Timer 1 fired (phase %u)\n", sim_time_string(), phase);
    if (phase == 1) {
      call Timer0.startOneShot(100);
      call Timer1.startOneShot(100);
    }
  }
}
munotes.in40

Practical 3: The TinyOS Architecture and Its Non-Preemptive Scheduler

Three things in it are nesC rather than C. task void taskA(); near the top declares a task before it is used, as a C prototype declares a function. post taskA() puts it in the queue and evaluates to an error_t, which is how the second post's answer is caught. And dbg("Sched", ...) prints on the debug channel named Sched, with sim_time_string() giving the simulated time.

The configuration, SchedulerDemoAppC.nc, gives the module its boot event and two independent timers:

configuration SchedulerDemoAppC {}
implementation {
  components MainC, SchedulerDemoC as App;
  components new TimerMilliC() as T0;
  components new TimerMilliC() as T1;

  App.Boot -> MainC;
  App.Timer0 -> T0;
  App.Timer1 -> T1;
}

The Makefile, the book's usual one:

COMPONENT=SchedulerDemoAppC
override GCC = gcc-10
export GCC
PFLAGS += -fgnu89-inline -fsigned-char
include $(MAKERULES)

And a script that runs one mote for half a simulated second, printing the channels named on its command line:

from TOSSIM import *
import sys

t = Tossim([])
t.randomSeed(1)
for channel in sys.argv[1:]:
    t.addChannel(channel, sys.stdout)
m = t.getNode(1)
m.bootAtTime(0)
while t.time() < t.ticksPerSecond() / 2:
    t.runNextEvent()

Step 3: build and run

$ make micaz sim 2>&1 | tail -1
*** Successfully built micaz TOSSIM library.
$ python2 run.py Sched
DEBUG (1): 0:0:0.000000000 booted: posting A, B and C
DEBUG (1): 0:0:0.000000000 posting A again returns 5 (EBUSY)
DEBUG (1): 0:0:0.000000000 Boot.booted ends
DEBUG (1): 0:0:0.000000010 task A starts, posts D
DEBUG (1): 0:0:0.000000010 task A ends
DEBUG (1): 0:0:0.000000020 task B runs
DEBUG (1): 0:0:0.000000030 task C runs
DEBUG (1): 0:0:0.000000050 task D runs
DEBUG (1): 0:0:0.097656260 Timer 0 fired (phase 1)
DEBUG (1): 0:0:0.097656270   whole job: piece 1
DEBUG (1): 0:0:0.097656270   whole job: piece 2
DEBUG (1): 0:0:0.097656270   whole job: piece 3
DEBUG (1): 0:0:0.097656270   whole job: piece 4
DEBUG (1): 0:0:0.097656270   whole job: piece 5
DEBUG (1): 0:0:0.097656280 Timer 1 fired (phase 1)
DEBUG (1): 0:0:0.195312510 Timer 0 fired (phase 2)
DEBUG (1): 0:0:0.195312520   chunked job: piece 1
DEBUG (1): 0:0:0.195312530 Timer 1 fired (phase 2)
DEBUG (1): 0:0:0.195312540   chunked job: piece 2
DEBUG (1): 0:0:0.195312560   chunked job: piece 3
DEBUG (1): 0:0:0.195312570   chunked job: piece 4
DEBUG (1): 0:0:0.195312580   chunked job: piece 5
munotes.in41

Practical 3: The TinyOS Architecture and Its Non-Preemptive Scheduler

Step 4: reading the boot

The first eight lines are rules 1 to 3, one after another.

Posting does not run anything. Boot.booted posted A, B and C and printed "Boot.booted ends" before any of them ran. Posting only puts a task in the queue; the scheduler runs it after the event handler that posted it has returned. All three posts, and the end of Boot.booted, happen at time zero; the first task runs 10 nanoseconds later, TOSSIM's fixed task latency (the TinyOS laboratory chapter found it in SimSchedulerBasicP.nc).

A second post of a waiting task is refused. Posting A again, while A was still in the queue, returned 5, which is EBUSY. Step 1's code showed why: pushTask finds A already waiting and says no. A ran once, not twice.

Tasks run first in, first out. A, B and C ran in the order they were posted, 10 nanoseconds apart.

A task runs to completion. Task A posted D in the middle of its body, and still printed "task A ends" before anything else ran. D did not run inside A; it went to the back of the queue.

And D ran after C, with a gap before it. When A posted D, B and C were already waiting, so D waited behind them. But D ran at 50 nanoseconds, not 40. The task at 40 nanoseconds is not one of ours: Timer0.startOneShot in Boot.booted posted a task of TinyOS's own timer system (VirtualizeTimerC's updateFromTimer), after C and before A had run. Your tasks and the operating system's tasks wait in one queue, which is why a task that runs for a long time delays the operating system too.

Step 5: one long task against a job cut into tasks

At 100 binary milliseconds both timers fall due. TinyOS's timer code fires one timer, then posts a task to look for the next (the TinyOS laboratory chapter showed this in VirtualizeTimerC.nc), so Timer 1's event reaches the application through the task queue like everything else.

Phase 1, the job as one task. Timer 0's event posted wholeJob, and the timer system then posted its own task to fire Timer 1. The queue held two tasks: the whole job, then the timer. The whole job ran all five pieces at one instant, 0.097656270, because a task runs to completion, and only then, at 0.097656280, did Timer 1's event run. Timer 1 waited for the entire job.

Phase 2, the job as five chained tasks. This time Timer 0's event posted oneChunk, which does one piece and posts itself again. After piece 1 the queue held the timer system's task ahead of piece 2, so Timer 1 fired at 0.195312530, between piece 1 and piece 2, and the job carried on afterwards. The gap between pieces 2 and 3, 20 nanoseconds instead of 10, is the timer system's task running once more to look for the next timer.

munotes.in42

Practical 3: The TinyOS Architecture and Its Non-Preemptive Scheduler

That is the whole practical lesson of a non-preemptive scheduler. Nothing can interrupt a task, so a long job must be cut into short tasks, each posting the next, so that other work gets its turn in between. TEP 106 gives the same pattern as the way to repeat work: "the end of the task logic can repost itself as need be." The price is that the job takes longer overall and must keep its own place in a variable, as chunk does here.

Step 6: what TOSSIM cannot show you

Two things about the real scheduler do not appear in this log, and a journal should say so rather than claim them.

On a real mote a long task makes events late. Each piece of the whole job takes real microseconds on a real ATmega128L, and every event that falls due meanwhile waits for the job to end. TOSSIM gives a task no simulated time at all: the five pieces of the whole job share one timestamp. So TOSSIM shows the order in which the scheduler runs things, which is the rule, and not the delay a long task causes on hardware, which is the consequence.

On a real mote an interrupt can pre-empt a task. A hardware interrupt, and the async code it runs, can stop a task in the middle, which is why shared variables touched by async code must be read and written inside atomic blocks. TOSSIM runs every event, interrupts included, one at a time in order of simulated time, so nothing ever pre-empts anything in the simulator. The claim this practical demonstrates is the one MU names: tasks are never pre-empted by other tasks.

Procedure

  1. Read TinyOS's main function and its scheduler's task loop and post command in the TinyOS tree.
  2. Write a module that posts three tasks at boot, posts one of them a second time, and has one task post another.
  3. Add two timers that fall due together, and a job of five pieces written first as one task and then as five chained tasks.
  4. Wire it with two TimerMilliC components, build with make micaz sim and run half a simulated second.
  5. Read the log: the order the tasks ran in, what the second post returned, and where Timer 1's event fell each time.

Observations

Time (seconds)What ranWhat it shows
0.000000000Boot.booted posts A, B, C; a second post of A returns 5 (EBUSY)posting defers; a waiting task cannot be posted twice
0.000000010task A, which posts D and then endsa task runs to completion
0.000000020, 0.000000030tasks B, Cfirst in, first out
0.000000040the timer system's own taskthe operating system shares the queue
0.000000050task DD waited behind everything posted before it
0.097656270the whole job, five pieces at one instantone task, uninterrupted
0.097656280Timer 1 firedthe event waited for the whole job
0.195312520 to 0.195312580the chunked job, one piece per taskeach piece a separate task
0.195312530Timer 1 fired, after piece 1the event got in between pieces
munotes.in43

Practical 3: The TinyOS Architecture and Its Non-Preemptive Scheduler

Result

TinyOS's execution model was read in its own source: main initialises the system, signals Boot.booted and hands the processor to the scheduler, whose loop runs one task at a time to completion and puts the microcontroller to sleep when the queue is empty. An application demonstrated the non-preemptive scheduler in TOSSIM: posted tasks ran after the posting event had returned, in first-in-first-out order; a task posted from inside another ran only after it and after the tasks already waiting, including one posted by TinyOS's own timer system; a second post of a waiting task was refused with EBUSY; and a timer event waited for a five-piece job written as one task, but ran after the first piece when the same job was written as five chained tasks.

Where marks are lost

Writing that posting a task runs it. It queues it. The log shows Boot.booted ending before any task started.

Calling the scheduler pre-emptive, or priority based. The basic TinyOS scheduler is first in, first out, and never interrupts a task.

Saying post returns FAIL for a duplicate. In TinyOS 2.1.2 it returns EBUSY, 5, and the log shows it.

A long loop inside one task. Everything else waits for it. Cut it into chunks that repost themselves.

Relying on FIFO order between tasks. TEP 106 says components must not assume it. If B must follow A, let A post B.

Claiming TOSSIM shows a task delaying an event in time. It shows the order only; a task takes no simulated time.

Forgetting the task declaration. A task used before it is defined must be declared, task void taskD();, as a function prototype is.

For the journal

Aim; event-driven against thread-based, in a few lines; commands, events and tasks, one line each; the four scheduler rules with TEP 106's words; the main function and the task loop, printed from the TinyOS tree; the module, the configuration, the Makefile and the script; the output; the observation table; the explanation of the 40-nanosecond task and of where Timer 1 fell in each phase; what TOSSIM cannot show; the result.

munotes.in44

Practical 3: The TinyOS Architecture and Its Non-Preemptive Scheduler

Quick revision

  • TinyOS is event-driven: one stack, no threads, sleep when there is nothing to do.
  • Commands go down (call), events come up (signal), tasks are deferred work (post).
  • Posting queues a task and returns; the scheduler runs it later.
  • Tasks run to completion and never pre-empt one another (TEP 106).
  • The basic scheduler is FIFO; do not rely on it: A should post B if B must follow A.
  • A post fails only if the task is already waiting: EBUSY, 5.
  • Application tasks and TinyOS's own tasks share one queue.
  • Cut long work into short tasks that repost themselves, so events get in between.
  • Interrupt (async) code can pre-empt a task on hardware; TOSSIM does not simulate that.
  • In TOSSIM a task takes no simulated time; each runs 10 ns after the previous.

Questions you must be able to answer

1. What does "non-preemptive" mean for the TinyOS scheduler? Once a task starts, the scheduler never stops it to run another task; the next task starts only when the running one returns.

2. What happens when you post a task that is already waiting in the queue? The post is refused with EBUSY, error code 5, and the task still runs once. A task can have only one place in the queue at a time.

3. In what order do tasks run, and may you rely on it? First in, first out, with the basic scheduler. TEP 106 says components must not rely on that: if one task must follow another, the first should post the second.

4. Task A posted D while B and C were waiting. When did D run, and why? After B, C and a task of TinyOS's own timer system: D went to the back of the queue, behind every task posted before it.

5. Why did Timer 1's event come after all five pieces in phase 1, and after the first piece in phase 2? In phase 1 the five pieces were one task, which runs to completion, so the timer's task waited behind it. In phase 2 each piece was its own task, and the timer's task was queued between piece 1 and piece 2.

6. Why does TinyOS use tasks and events instead of threads? Threads need a stack each, and a mote has kilobytes of RAM. Events and run-to-completion tasks all share one stack, and the processor sleeps whenever the queue is empty.

7. What can interrupt a task on a real mote? A hardware interrupt, running async code. Variables shared with such code must be accessed in atomic blocks.

8. Why did the five pieces of the whole job all print the same time? Because TOSSIM gives a task no simulated time. It simulates the order of events, not how long the processor spends on each.

munotes.in45

Practical 3: The TinyOS Architecture and Its Non-Preemptive Scheduler

9. What does the scheduler do when there are no tasks? It puts the microcontroller to sleep, McuSleep.sleep(), until an interrupt occurs, and that sleep is where a mote saves most of its energy.

Contents This chapter on its own page

munotes.in46

Chapter Six

Practical 4: The nesC Programming Model: Modules, Configurations and Wiring

Syllabus topic Module 1, "Implementation of nesC Programming Model: Create a basic nesC application using modules and configurations to understand component wiring and interface binding."

Aim

To create a basic nesC application out of modules and configurations, and through it to understand component wiring and interface binding.

What you need to know before you start

A nesC program is not one file of functions. It is a set of components joined together, and the joining is written separately from the code. That separation is the whole idea of the language, and six words carry it.

Interface. A named set of functions two components use to talk to each other. An interface has two kinds of function in it: commands, which the user of the interface calls, and events, which the provider of the interface signals back. Timer<TMilli> is an interface: its user calls the command startPeriodic and receives the event fired.

Provides and uses. A component that provides an interface implements its commands and may signal its events. A component that uses an interface may call its commands and must implement its events. One interface, two sides, and each side has obligations.

Module. A component with code in it: variables, functions, and the bodies of the commands and events it is responsible for.

Configuration. A component with no code of its own, only a list of other components and the connections between them. The application itself is a configuration, the one the Makefile names in COMPONENT=.

Wiring. A connection between one component's use of an interface and another component's provision of it, written in a configuration as User.Interface -> Provider.Interface. The arrow points from user to provider. Wiring is how a call to a command in one component reaches the code that implements it in another.

Binding. Wiring is checked and fixed when the program is compiled, not while it runs. A call is bound to exactly the code the configuration connected it to, and nesC checks the interface types on both sides of every arrow. There are no function pointers to go wrong at run time.

Four more pieces of syntax appear in this practical, each explained where it is used: = in a configuration, as to rename, generic components created with new, and fan-out.

The application

A timer ticks every quarter of a second. On every tick the application adds one to two tallies, a fast one that announces every 2nd count and a slow one that announces every 5th. The tallies are two copies of one component we write ourselves, joined to the application through an interface we also write.

The wiring of CountingAppC. Each solid arrow runs from the component that uses an interface to the one that provides it; the dashed line inside each TallyC is the equals sign that hands TallyP's interface out.

Figure 6.1 CountingAppC: components and their wiring

Make a folder for it:

$ mkdir ~/Counting
$ cd ~/Counting

Step 1: the interface

Tally.nc:

interface Tally {
  command void add();
  command uint16_t total();
  event void reached(uint16_t value);
}
munotes.in47

Practical 4: The nesC Programming Model: Modules, Configurations and Wiring

Two commands and one event. The component that uses Tally may call add and total, and must write a handler for reached. The component that provides Tally must implement add and total, and may signal reached. An interface file has no code: it is the contract between the two sides.

Step 2: a module that provides it

TallyP.nc:

generic module TallyP(uint16_t limit) {
  provides interface Tally;
}
implementation {
  uint16_t count = 0;

  command void Tally.add() {
    count++;
    if (count % limit == 0)
      signal Tally.reached(count);
  }

  command uint16_t Tally.total() {
    return count;
  }
}

The first part, before implementation, is the module's signature: what it provides and uses, and nothing else of it is visible outside. The implementation holds its state, count, and the bodies of the two commands the interface obliges it to implement. When the count reaches a multiple of limit, it signals the event reached to whoever uses this interface.

generic with a parameter makes this a template rather than a single component. Every time a configuration writes new TallyP(5), the compiler makes a separate copy with its own count and its own limit. Without generic, there would be exactly one TallyP in the whole program, and every user would share its count.

The name ends in P because TinyOS's own convention is that a component ending in P is private, an implementation, and one ending in C is the public component other code should wire to.

Step 3: a configuration that hands it out

TallyC.nc:

generic configuration TallyC(uint16_t limit) {
  provides interface Tally;
}
implementation {
  components new TallyP(limit) as P;
  Tally = P.Tally;
}

This is the public face of the tally. It provides Tally, but has no code, so it cannot implement it itself: it creates a TallyP and says, with the equals sign, that the Tally it provides is TallyP's Tally.

That is the difference between the two operators, and it is the most asked question about nesC:

->=
Connectsa component that uses an interface to one that provides itan interface in THIS configuration's own signature to one inside it
Reads as"is served by""is the same as"
Directionfrom user to providerbetween an outside and an inside
Example hereCountingC.Fast -> Fast;Tally = P.Tally;

as P gives the new component a local name, so the wiring can refer to it.

Step 4: a module that uses it, twice

CountingC.nc:

module CountingC {
  uses interface Boot;
  uses interface Timer<TMilli>;
  uses interface Tally as Fast;
  uses interface Tally as Slow;
}
implementation {
  event void Boot.booted() {
    call Timer.startPeriodic(256);
  }

  event void Timer.fired() {
    call Fast.add();
    call Slow.add();
    if (call Slow.total() == 10)
      call Timer.stop();
  }

  event void Fast.reached(uint16_t value) {
    dbg("Count", "%s Fast reached %u\n", sim_time_string(), value);
  }

  event void Slow.reached(uint16_t value) {
    dbg("Count", "%s Slow reached %u\n", sim_time_string(), value);
  }
}
munotes.in48

Practical 4: The nesC Programming Model: Modules, Configurations and Wiring

The module uses the Tally interface twice, so each use needs its own name: as Fast and as Slow. Inside the module the names are all that is visible. call Fast.add() means "the add of whatever Fast is wired to"; the module does not know, and does not need to know, which component that will be. And because it uses both, it must implement the reached event of both, Fast.reached and Slow.reached.

startPeriodic(256) is 256 of TinyOS's binary milliseconds, a quarter of a second. After ten ticks the slow tally's total is 10 and the timer is stopped.

Step 5: the configuration that is the application

CountingAppC.nc:

configuration CountingAppC {}
implementation {
  components MainC, CountingC, new TimerMilliC();
  components new TallyC(2) as Fast;
  components new TallyC(5) as Slow;

  CountingC.Boot -> MainC;
  CountingC.Timer -> TimerMilliC;
  CountingC.Fast -> Fast;
  CountingC.Slow -> Slow;
}

Its signature is empty, {}, because nothing uses the application. Its implementation lists every component in it and wires every interface CountingC uses:

  • Boot to MainC, TinyOS's component that boots the system and signals Boot.booted.
  • Timer to a new TimerMilliC: TinyOS's timers are generic too, so each new is a timer of its own.
  • Fast to a TallyC made with limit 2, and Slow to one made with limit 5.

The figure at the top of this practical is exactly these four lines drawn out.

And the Makefile names this configuration as the application:

COMPONENT=CountingAppC
override GCC = gcc-10
export GCC
PFLAGS += -fgnu89-inline -fsigned-char
include $(MAKERULES)

Step 6: build and run

A script that runs one mote for three simulated seconds, run.py:

from TOSSIM import *
import sys

t = Tossim([])
t.randomSeed(1)
t.addChannel("Count", sys.stdout)
m = t.getNode(1)
m.bootAtTime(0)
while t.time() < 3 * t.ticksPerSecond():
    t.runNextEvent()
$ make micaz sim 2>&1 | tail -1
*** Successfully built micaz TOSSIM library.
$ python2 run.py
DEBUG (1): 0:0:0.500000010 Fast reached 2
DEBUG (1): 0:0:1.000000010 Fast reached 4
DEBUG (1): 0:0:1.250000010 Slow reached 5
DEBUG (1): 0:0:1.500000010 Fast reached 6
DEBUG (1): 0:0:2.000000010 Fast reached 8
DEBUG (1): 0:0:2.500000010 Fast reached 10
DEBUG (1): 0:0:2.500000010 Slow reached 10

The ticks come every quarter of a second, and the log is exactly what the wiring says it should be. The fast tally announces every second tick (counts 2, 4, 6, 8, 10 at 0.5, 1.0, 1.5, 2.0 and 2.5 seconds) and the slow tally every fifth (5 at 1.25 seconds, 10 at 2.5). After the tenth tick the slow tally's total is 10 and the timer stops, so nothing prints after 2.5 seconds although the script ran for three.

munotes.in49

Practical 4: The nesC Programming Model: Modules, Configurations and Wiring

Two copies of one component, each with its own count and its own limit: that is what new did. And CountingC could tell their events apart because they arrive through two differently named uses, Fast.reached and Slow.reached, of one interface type.

Step 7: the rules, each shown by breaking it

Every rule in the first section of this practical is enforced by the compiler, and the quickest way to learn them is to break each one and read what nesC says. Keep a copy of the two files about to be broken:

$ cp CountingAppC.nc CountingAppC.good
$ cp TallyP.nc TallyP.good

Rule: every interface a component uses must be wired. Delete the line CountingC.Slow -> Slow; from the configuration and build:

$ sed -i '/CountingC.Slow -> Slow;/d' CountingAppC.nc
$ make micaz sim 2>&1 | grep -E 'not connected|Error'
CountingC.nc:14: Slow.add not connected
CountingC.nc:15: Slow.total not connected
make: *** [/home/student/tinyos-2.1.2/support/make/sim.extra:69: sim-exe] Error 1
$ cp CountingAppC.good CountingAppC.nc

CountingC calls Slow.add on line 14 and Slow.total on line 15, and nothing now provides them, so the build stops. nesC reports it at the call, in the module, although the mistake is in the configuration. That is worth remembering: "not connected" means look at the wiring, not at the line it names. (A module can write a default body for a command it uses, which runs when nothing is wired; without one, an unwired call is an error.)

Rule: a provider must implement every command of the interface it provides. Delete the total command from TallyP and build:

$ sed -i '/command uint16_t Tally.total/,/^  }/d' TallyP.nc
$ make micaz sim 2>&1 | grep -E 'not implemented|Error'
TallyP.nc:4: `Tally.total' not implemented
make: *** [/home/student/tinyos-2.1.2/support/make/sim.extra:69: sim-exe] Error 1
$ cp TallyP.good TallyP.nc

TallyP promised, by providing Tally, to implement both of its commands. The error names the line of the promise, line 4, provides interface Tally;.

Rule: both ends of a wire must be the same interface. Wire Slow, a Tally, to the timer, which provides Timer<TMilli>:

$ sed -i 's/CountingC.Slow -> Slow;/CountingC.Slow -> TimerMilliC;/' CountingAppC.nc
$ make micaz sim 2>&1 | grep -E 'no match|Error'
CountingAppC.nc:10: no match
make: *** [/home/student/tinyos-2.1.2/support/make/sim.extra:69: sim-exe] Error 1
$ cp CountingAppC.good CountingAppC.nc

Line 10 is the wire, and "no match" is nesC saying it found no interface of type Tally on the timer to connect it to. This is what binding at compile time buys: a wrong connection cannot reach the mote.

Not an error: one use wired to two providers. Keep the line for Slow and add a second wire for Fast, to the slow tally as well:

munotes.in50

Practical 4: The nesC Programming Model: Modules, Configurations and Wiring

$ sed -i 's/CountingC.Slow -> Slow;/CountingC.Slow -> Slow;\n  CountingC.Fast -> Slow;/' CountingAppC.nc
$ tail -4 CountingAppC.nc
  CountingC.Fast -> Fast;
  CountingC.Slow -> Slow;
  CountingC.Fast -> Slow;
}
$ make micaz sim 2>&1 | grep -E 'fan out|Success'
nesc1: warning: calls to Fast.total in CountingC fan out, but there is no combine function specified for the return type
*** Successfully built micaz TOSSIM library.
$ python2 run.py
DEBUG (1): 0:0:0.500000010 Fast reached 2
DEBUG (1): 0:0:0.750000010 Slow reached 5
DEBUG (1): 0:0:0.750000010 Fast reached 5
DEBUG (1): 0:0:1.000000010 Fast reached 4
DEBUG (1): 0:0:1.250000010 Slow reached 10
DEBUG (1): 0:0:1.250000010 Fast reached 10
$ cp CountingAppC.good CountingAppC.nc

This is fan-out: Fast is now wired to both tallies. It builds, and the log changes in two ways that show exactly what fan-out means.

A command on a fanned-out use is called on every provider. Each call Fast.add() now adds to the fast tally AND the slow one, so the slow tally counts twice per tick and reaches 5 at the third tick, 0.75 seconds, instead of 1.25.

An event from a provider reaches every user wired to it. When the slow tally reached 5 it signalled reached once, and both Slow.reached and Fast.reached in CountingC ran, because both uses are now wired to it: two lines at 0.75 seconds, "Slow reached 5" and "Fast reached 5".

The warning is about total, which returns a value. When one call reaches two providers there are two results, and a program must say how to combine them. The nesC reference manual's rule is that the result type must have a combining function "or a compile-time error occurs"; TinyOS's error_t has one, ecombine, which is why commands returning error_t fan out safely all over TinyOS. uint16_t has none. The nesC reference manual calls this an error, and nesC 1.3.5 lets it through with a warning, which is the more dangerous of the two: the build succeeds, and a call to Fast.total() would return a value the program never chose. Treat that warning as an error.

Step 8: a name TinyOS already uses

Suppose the interface had been called Counter, which is what most people would call it. Add a file of that name, even one the application never uses:

$ printf 'interface Counter {\n  command void add();\n}\n' > Counter.nc
$ make micaz sim 2>&1 | grep -m4 -E 'In component|type arguments'
In component `AlarmCounterMilliP':
/home/student/tinyos-2.1.2/tos/platforms/mica/AlarmCounterMilliP.nc:29: unexpected type arguments
In component `Atm128AlarmAsyncC':
/home/student/tinyos-2.1.2/tos/chips/atm128/timer/Atm128AlarmAsyncC.nc:27: unexpected type arguments
$ rm Counter.nc
$ make micaz sim 2>&1 | tail -1
*** Successfully built micaz TOSSIM library.

The errors are not in the application at all. They are in AlarmCounterMilliP and Atm128AlarmAsyncC, parts of TinyOS's own timer system, which use a TinyOS interface also called Counter, one that takes type arguments. nesC looks for every component and interface in the application's own folder first, so the file Counter.nc sitting there replaced TinyOS's Counter for the whole build, and every TinyOS component that uses it broke. Deleting the file mends it.

munotes.in51

Practical 4: The nesC Programming Model: Modules, Configurations and Wiring

nesC has no namespaces: a component or interface name means one thing across the whole program. Before naming anything, check it is not already a TinyOS name:

$ find $TOSDIR -name 'Counter.nc' | head -3
/home/student/tinyos-2.1.2/tos/lib/timer/Counter.nc
$ find $TOSDIR -name 'Tally.nc' | wc -l
0

Procedure

  1. Write the interface Tally, with two commands and one event.
  2. Write TallyP, a generic module that provides Tally.
  3. Write TallyC, a generic configuration that provides Tally by exporting TallyP's with =.
  4. Write CountingC, a module that uses Tally twice, as Fast and as Slow, with a timer and the boot event.
  5. Write CountingAppC, the top-level configuration, making two tallies with new and wiring everything with ->.
  6. Build with make micaz sim and run three simulated seconds.
  7. Break the wiring four ways and read what nesC says each time; restore the file after each.
  8. Name an interface of your own Counter and read what happens.

Observations

RunWhat nesC or the simulator said
The application as writtenFast reached 2, 4, 6, 8, 10 at 0.5 to 2.5 s; Slow reached 5 at 1.25 s and 10 at 2.5 s; the timer stopped after ten ticks
CountingC.Slow left unwirederror: Slow.add not connected, Slow.total not connected
total missing from TallyPerror: `Tally.total' not implemented
Slow wired to the timererror: no match
Fast wired to both tallieswarning: fan out with no combine function; built; the slow tally counted twice per tick and both handlers received its events
A file named Counter.nc in the foldererrors in TinyOS's own timer components: unexpected type arguments

Result

A nesC application was built from an interface, two modules and two configurations. TallyP, a generic module, provides the interface Tally; TallyC, a generic configuration, provides it by exporting TallyP's with =; CountingC uses it twice under the names Fast and Slow; and CountingAppC, the application's configuration, creates two tallies with new and wires every used interface to a provider with ->. The run printed exactly the events the wiring determined. Breaking the wiring showed that nesC binds every call at compile time: an unwired use, an unimplemented command and a wire between different interfaces are all compile-time errors, and fan-out calls every provider and delivers a provider's events to every user. An interface named after a TinyOS interface replaced it for the whole program.

munotes.in52

Practical 4: The nesC Programming Model: Modules, Configurations and Wiring

Where marks are lost

Getting the arrow backwards. User.Interface -> Provider. The arrow points at the component that implements the commands.

Writing -> for an export, or = for a connection. = joins this configuration's own interface to one inside it; -> joins a user to a provider.

Implementing the events of an interface you provide. It is the other way round: a provider implements the commands and signals the events; a user calls the commands and implements the events.

Using one interface twice without as. Two uses of Tally need two names.

Forgetting new for a generic component. components TallyC; is not a component; components new TallyC(2) as Fast; is.

Naming an interface or component after a TinyOS one. Counter, Timer, Leds, Send: check with find $TOSDIR -name.

Reading "not connected" as a mistake in the module. It is reported at the call; the fault is in the configuration.

Ignoring the fan-out warning. It builds, and the result of the call is not one the program chose.

For the journal

Aim; interface, provides, uses, module, configuration, wiring and binding, a line each; the table of -> against =; all five source files, the Makefile and the script; the wiring diagram; the output with its explanation; the four broken variants, each with the change made and the exact message nesC gave; the Counter clash; the observation table; the result.

Quick revision

  • An interface is a contract of commands (called by its user) and events (signalled by its provider).
  • A provider implements the commands and signals the events; a user calls the commands and implements the events.
  • A module has code; a configuration has only components and wiring.
  • User.Interface -> Provider connects; Interface = Inner.Interface exports.
  • as renames: two uses of one interface need two names.
  • generic makes a template; new makes a copy with its own state.
  • Wiring is bound at compile time: unwired uses, unimplemented commands and mismatched wires are errors.
  • Fan-out calls every provider; a non-void result needs a combining function (error_t has ecombine).
  • Names are global: a file named after a TinyOS interface replaces it for the whole build.
  • By convention a name ending in P is an implementation and one ending in C is the component to wire to.

Questions you must be able to answer

1. What is the difference between a module and a configuration? A module contains code: variables and the bodies of commands and events. A configuration contains no code, only the list of components it uses and the wiring between them.

munotes.in53

Practical 4: The nesC Programming Model: Modules, Configurations and Wiring

2. Which side of an interface implements the commands, and which the events? The provider implements the commands and may signal the events. The user may call the commands and must implement the events.

3. What is the difference between -> and =? -> connects a component that uses an interface to one that provides it. = says that an interface in this configuration's own signature is the same as one inside it, which is how a configuration hands out an interface it has no code to implement.

4. What does new TallyC(2) as Fast do? It creates a separate copy of the generic configuration TallyC, and through it of TallyP, with the parameter 2, and gives that copy the local name Fast.

5. Why is wiring called binding at compile time, and what does it give you? Because every call is connected to its implementation when the program is compiled, and nesC checks the interface types at both ends. A wrong or missing connection is a compile-time error rather than a crash on the mote.

6. What happened when Fast was wired to both tallies? Every Fast.add() was called on both, so the slow tally counted twice per tick, and when the slow tally signalled reached, both CountingC handlers ran.

7. What is a combining function? The function that merges the results when a call fans out to several providers. error_t has ecombine; a type with none, such as uint16_t, gives a warning in nesC 1.3.5, and the reference manual calls it an error.

8. Why did a file called Counter.nc break TinyOS's timers? Because nesC searches the application's folder first and has no namespaces, so that file replaced TinyOS's own Counter interface for every component in the build.

Contents This chapter on its own page

munotes.in54

Chapter Seven

Practical 5: Events, Commands, Tasks and Split-Phase Execution

Syllabus topic Module 1, "Implementation of Events, Commands, and Tasks in TinyOS: Develop a TinyOS program that toggles LEDs using events and tasks to demonstrate split-phase execution."

Aim

To develop a TinyOS program that toggles LEDs using events and tasks, and through it to demonstrate split-phase execution.

What you need to know before you start

Practical 3 introduced the three kinds of code in a TinyOS program. This practical makes each of them do one visible thing, an LED toggle, so that you can see from the log which kind ran when.

Written asRunsUsed for
Commandcommand void Leds.led0Toggle(), invoked with callat once, inside the callerasking a component to do something
Eventevent void Timer.fired(), invoked with signalat once, inside the component that signals ittelling a component that something happened
Tasktask void blink(), invoked with postlater, when the scheduler reaches itwork that can wait, or that is too long to do inside an event

Split-phase execution

Some operations take time: reading a sensor, sending a packet, writing to flash. In an ordinary program a function that takes time blocks: value = read_sensor(); does not return until the reading is ready, and the program waits.

TinyOS cannot wait. It has one stack and no threads (Practical 3), so a command that sat waiting would stop the whole mote: no timer, no packet, no other work, for as long as the sensor took. So every operation that takes time is split into two phases:

  1. The request, a command, which starts the operation and returns at once. Its return value only says whether the request was accepted.
  2. The completion, an event, signalled later when the operation has finished, carrying the result.

Between the two, the caller's code has returned and the processor is free to do anything else, or to sleep. Philip Levis's TinyOS Programming (2006) explains where the idea comes from. Hardware already works this way: it "is split-phase in that completion of a request is a callback", an interrupt when the sensor's conversion is done. TinyOS keeps the same shape in software, and so its split-phase interfaces are "bidirectional: there is a downcall to start the operation, and an upcall that signifies the operation is complete."

TinyOS's own interface for reading a sensor is the standard example. Print it from the TinyOS tree:

$ grep -v '^ \*\|^/\*\|^$' $TOSDIR/interfaces/Read.nc
interface Read<val_t> {
  /**
   * Initiates a read of the value.
   *
   * @return SUCCESS if a readDone() event will eventually come back.
   */
  command error_t read();
  /**
   * Signals the completion of the read().
   *
   * @param result SUCCESS if the read() was successful
   * @param val the value that has been read
   */
  event void readDone( error_t result, val_t val );
}

read() is the request and returns an error_t; readDone(result, val) is the completion and carries the value. Radio sends (send and sendDone), starting a radio (start and startDone) and flash writes all follow the same pattern, and you have already used two of them in Practical 2.

munotes.in55

Practical 5: Events, Commands, Tasks and Split-Phase Execution

The application

Every second, a timer event does three things: it toggles LED 0 itself, it posts a task that will toggle LED 1, and it asks a sensor for a reading. The sensor is a component of our own that takes a tenth of a second to answer, so its answer, which toggles LED 2, arrives as an event long after the timer's event has returned.

Make a folder:

$ mkdir ~/SplitPhase
$ cd ~/SplitPhase

Step 1: a component that takes time to answer

SlowSensorC.nc provides TinyOS's own Read interface, so any code that can read a real sensor can read this one:

/* A sensor that takes 100 binary milliseconds to produce a reading,
   written as a split-phase component: read() starts the conversion and
   returns at once; readDone() delivers the value when it is ready. */
module SlowSensorC {
  provides interface Read<uint16_t>;
  uses interface Timer<TMilli> as Conversion;
}
implementation {
  bool busy = FALSE;
  uint16_t sample = 500;

  command error_t Read.read() {
    if (busy)
      return EBUSY;              /* one conversion at a time */
    busy = TRUE;
    call Conversion.startOneShot(100);
    return SUCCESS;              /* accepted: the value comes later */
  }

  event void Conversion.fired() {
    busy = FALSE;
    sample += 7;
    signal Read.readDone(SUCCESS, sample);
  }
}

read() does not produce a value. It checks that no conversion is already running, starts a one-shot timer standing in for the sensor's conversion time, and returns SUCCESS, meaning "accepted". The value is produced in Conversion.fired, a tenth of a second later, and handed to the caller by signalling readDone. A second read() while a conversion is running is refused with EBUSY, exactly as a real sensor driver refuses one.

Step 2: the application module

SplitPhaseC.nc:

module SplitPhaseC {
  uses interface Boot;
  uses interface Timer<TMilli>;
  uses interface Leds;
  uses interface Read<uint16_t> as Sensor;
}
implementation {
  task void blink() {
    dbg("Split", "%s task blink runs: toggle LED 1\n", sim_time_string());
    call Leds.led1Toggle();
  }

  event void Boot.booted() {
    call Timer.startPeriodic(1024);
  }

  event void Timer.fired() {
    error_t first, second;
    dbg("Split", "%s Timer.fired starts: toggle LED 0\n", sim_time_string());
    call Leds.led0Toggle();
    post blink();
    first = call Sensor.read();
    second = call Sensor.read();
    dbg("Split", "%s read() returned %u, a second read() returned %u\n",
        sim_time_string(), first, second);
    dbg("Split", "%s Timer.fired ends\n", sim_time_string());
  }

  event void Sensor.readDone(error_t result, uint16_t value) {
    dbg("Split", "%s readDone: value %u, toggle LED 2\n", sim_time_string(), value);
    call Leds.led2Toggle();
  }
}

One LED per kind of code: LED 0 is toggled inside an event (Timer.fired), LED 1 inside a task posted from that event, and LED 2 inside the completion event of a split-phase read. Every step prints the simulated time, so the log is a timeline.

munotes.in56

Practical 5: Events, Commands, Tasks and Split-Phase Execution

Step 3: the wiring, the Makefile and the script

SplitPhaseAppC.nc. The sensor needs a timer of its own for its conversion, and it gets one here, in the application's configuration, because SlowSensorC is a module and cannot create components:

configuration SplitPhaseAppC {}
implementation {
  components MainC, LedsC, SplitPhaseC as App, SlowSensorC;
  components new TimerMilliC() as Tick;
  components new TimerMilliC() as ConversionTimer;

  App.Boot -> MainC;
  App.Timer -> Tick;
  App.Leds -> LedsC;
  App.Sensor -> SlowSensorC;
  SlowSensorC.Conversion -> ConversionTimer;
}
COMPONENT=SplitPhaseAppC
override GCC = gcc-10
export GCC
PFLAGS += -fgnu89-inline -fsigned-char
include $(MAKERULES)

The script prints two channels: ours, Split, and LedsC, on which TinyOS's simulated LED component reports every change of state:

from TOSSIM import *
import sys

t = Tossim([])
t.randomSeed(1)
t.addChannel("Split", sys.stdout)
t.addChannel("LedsC", sys.stdout)
m = t.getNode(1)
m.bootAtTime(0)
while t.time() < 2.5 * t.ticksPerSecond():
    t.runNextEvent()

Step 4: build and run

$ make micaz sim 2>&1 | tail -1
*** Successfully built micaz TOSSIM library.
$ python2 run.py
DEBUG (1): 0:0:1.000000010 Timer.fired starts: toggle LED 0
DEBUG (1): LEDS: Led0 on.
DEBUG (1): 0:0:1.000000010 read() returned 0, a second read() returned 5
DEBUG (1): 0:0:1.000000010 Timer.fired ends
DEBUG (1): 0:0:1.000000020 task blink runs: toggle LED 1
DEBUG (1): LEDS: Led1 on.
DEBUG (1): 0:0:1.097656260 readDone: value 507, toggle LED 2
DEBUG (1): LEDS: Led2 on.
DEBUG (1): 0:0:2.000000010 Timer.fired starts: toggle LED 0
DEBUG (1): LEDS: Led0 off.
DEBUG (1): 0:0:2.000000010 read() returned 0, a second read() returned 5
DEBUG (1): 0:0:2.000000010 Timer.fired ends
DEBUG (1): 0:0:2.000000020 task blink runs: toggle LED 1
DEBUG (1): LEDS: Led1 off.
DEBUG (1): 0:0:2.097656260 readDone: value 514, toggle LED 2
DEBUG (1): LEDS: Led2 off.

Step 5: reading the timeline

Take the first second, line by line. Each LEDS: line is TinyOS's own simulated LED component reporting a change, and it prints no time, so it belongs to the time of the line above it.

1.000000010, inside the event. The timer fired and Timer.fired began. It toggled LED 0 with a command, call Leds.led0Toggle(), and LED 0 came on at once: a command runs immediately, inside its caller.

1.000000010, the request. Still inside Timer.fired, the first read() returned 0, which is SUCCESS. It did not return a reading. It returned "accepted, the value will come". The second read(), made while the first conversion was running, returned 5, EBUSY: the sensor does one conversion at a time and said so at once rather than making the caller wait.

1.000000010, the event ends. Timer.fired returned. Nothing it asked for, neither the task nor the reading, had happened yet.

munotes.in57

Practical 5: Events, Commands, Tasks and Split-Phase Execution

1.000000020, the task. Ten nanoseconds later the scheduler ran blink, which toggled LED 1. A task runs after the code that posted it has returned, and in TOSSIM that is one task latency later.

1.097656260, the completion. The reading arrived, value 507, as the event readDone, and its handler toggled LED 2. That is 100 of TinyOS's binary milliseconds after the request, 0.09765625 seconds (100/1024), plus the task latency: exactly the conversion time SlowSensorC stood for.

So the one timer event started three things and finished none of them itself, and each finished in its own way: the command at once, the task a moment later, the split-phase read when its operation was done. The second second repeats the pattern and toggles every LED back off, with the next reading, 514.

LEDToggled byKind of codeWhen, after the timer fired
0Timer.firedevent handlerat once
1blinktask posted by the event10 nanoseconds later (one task)
2Sensor.readDonecompletion event of a split-phase call0.0977 seconds later (the conversion)

Step 6: why not simply wait?

Imagine read() written the ordinary way, returning the value after 100 milliseconds of waiting. Three things would go wrong on a mote, and they are why TinyOS is built the other way.

Everything else would stop. TinyOS has one stack and a non-preemptive scheduler (Practical 3). A command that waited would hold the processor for the whole conversion: no timer event, no received packet, no other task, for 100 milliseconds.

The waiting would cost energy. A processor spinning in a loop until the sensor is ready is awake the whole time. With split-phase, the processor has nothing to do between the request and the completion, so the scheduler puts it to sleep (Practical 3, McuSleep.sleep()), and Practical 1 showed what sleeping is worth.

Threads would cost RAM. The usual way to wait without stopping everything is threads, and Levis's book gives the reason TinyOS will not: "Each thread has its own private stack which has to be stored when a thread is waiting or idle", and "Early versions of TinyOS ran in 512 bytes of RAM."

What split-phase costs you is program structure. The code that asks for a reading and the code that uses it are in two different functions, and anything the second needs from the first must be kept in module variables, as busy is kept in SlowSensorC. Every TinyOS program is written this way, and reading one means following requests to their completion events.

Procedure

  1. Print TinyOS's Read interface and identify its request and its completion.
  2. Write SlowSensorC, a split-phase module that provides Read<uint16_t>, starts a conversion in read(), refuses a second request with EBUSY, and signals readDone when a timer standing in for the conversion fires.
  3. Write SplitPhaseC, whose timer event toggles LED 0, posts a task that toggles LED 1, and requests a reading whose completion toggles LED 2.
  4. Wire both, giving the sensor its own timer, and build with make micaz sim.
  5. Run two and a half simulated seconds with the Split and LedsC channels on, and read the timeline.
munotes.in58

Practical 5: Events, Commands, Tasks and Split-Phase Execution

Observations

Time (seconds)EventKind
1.000000010Timer.fired starts; LED 0 onevent, command
1.000000010first read() returns 0 (SUCCESS); second returns 5 (EBUSY)split-phase request
1.000000010Timer.fired ends
1.000000020task blink; LED 1 ontask
1.097656260readDone, value 507; LED 2 onsplit-phase completion
2.000000010 to 2.097656260the same cycle; all three LEDs off; value 514

Result

A TinyOS program was written that toggles one LED in a timer event, one in a task posted by that event, and one in the completion event of a split-phase read from a sensor component of our own that takes 100 binary milliseconds to answer. The TOSSIM timeline showed the command acting at once inside the event, the event returning before either the task or the reading had happened, the task running one task latency later, and the reading arriving as readDone 0.0977 seconds after the request, which is the conversion time; a second request during the conversion was refused at once with EBUSY. This is split-phase execution: a request that returns immediately and a completion delivered later as an event, so that the mote is never held waiting.

Where marks are lost

Expecting read() to return the value. It returns an error_t saying whether the request was accepted. The value arrives in readDone.

Using the value in the same function that called read(). It does not exist yet. The code that uses it belongs in readDone.

Calling read() again before readDone. The component is busy and answers EBUSY; check the return value.

Writing a busy-wait loop instead of split-phase. It holds the processor, stops every other event and keeps the mote awake.

Doing long work inside an event handler. Post a task for it, as Timer.fired posted blink.

Forgetting to give the sensor its own timer in the configuration. SlowSensorC uses Timer as Conversion; unwired, the build stops with "not connected" (Practical 4).

For the journal

Aim; commands, events and tasks in a table; blocking against split-phase, with the two quotations; TinyOS's Read interface; the four files, the Makefile and the script; the output; the timeline table with an explanation of each line; why TinyOS does not wait; the result.

munotes.in59

Practical 5: Events, Commands, Tasks and Split-Phase Execution

Quick revision

  • Command: called with call, runs at once in the caller.
  • Event: signalled with signal, runs at once in the component that signals it.
  • Task: posted with post, runs later, after the poster returns.
  • Split-phase: a request command that returns at once, and a completion event carrying the result.
  • read() returns SUCCESS (accepted) or an error such as EBUSY, never the value.
  • readDone(result, val) delivers the value.
  • Other split-phase pairs: send/sendDone, start/startDone.
  • Why: one stack, no threads, a non-preemptive scheduler, and a processor that should sleep while hardware works.
  • The cost: the request and its result live in different functions; state is kept in module variables.

Questions you must be able to answer

1. What is split-phase execution? An operation that takes time is divided into a request, a command that starts it and returns at once, and a completion, an event signalled later with the result.

2. What did the first read() return, and what did it mean? 0, SUCCESS: the request was accepted and a readDone would follow. It did not return the reading.

3. Why did the second read() return 5? 5 is EBUSY. A conversion was already running, and SlowSensorC does one at a time.

4. Put LED 0, LED 1 and LED 2 in the order they changed, and say why. LED 0 first, toggled by a command inside the timer event; LED 1 next, in a task that ran after the event returned; LED 2 last, in readDone, when the conversion finished 0.0977 seconds later.

5. How long after the request did the reading arrive, and where does the figure come from? 0.09765625 seconds plus 10 nanoseconds: SlowSensorC's conversion timer is 100 binary milliseconds, 100/1024 of a second, and the completion is delivered one task latency after the timer fires.

6. Why does TinyOS not simply block until a sensor is ready? A blocked command would hold the only stack and the non-preemptive scheduler, stopping every other event, and keep the processor awake; threads would avoid that only at a cost in RAM a mote does not have.

7. Name two other split-phase interfaces you have used. AMSend, with send and sendDone, and SplitControl, with start and startDone, both in Practical 2.

8. Where do you use a value that comes from a split-phase read? In the completion event, readDone, or in a task posted from it; never in the function that called read().

Contents This chapter on its own page

munotes.in60

Chapter Eight

Practical 6: Simulating a Single Mote in TOSSIM

Syllabus topic Module 1, "Simulation of Single Mote using TOSSIM: Simulate a single sensor node in TOSSIM and observe execution logs to understand the TinyOS runtime environment."

Aim

To simulate a single sensor node in TOSSIM, to observe its execution logs, and through them to understand the runtime environment TinyOS gives an application.

What you need to know before you start

What TOSSIM is

TOSSIM is a discrete-event simulator. It does not run a mote's program in real time. It keeps a queue of events, each stamped with the simulated time at which it happens (a timer expiring, a task being run, a packet arriving), and runNextEvent() takes the earliest one off the queue, moves the simulated clock to its time and runs it. The clock does not tick along by itself: it jumps from event to event. Nothing happens between two events, so an hour of a sleeping mote costs almost nothing to simulate.

Time is counted in ticks, and there are ten thousand million of them in a simulated second (the TinyOS laboratory chapter printed ticksPerSecond()), so one tick is a tenth of a nanosecond.

What the build produced

make micaz sim compiled your application, together with every part of TinyOS it uses, into a library for the PC, with the MICAz's hardware replaced by simulated versions. The simulated hardware is detailed: the MICAz's timer chip is simulated register by register, and LedsC prints each change of an LED. Two things are not simulated, and every log must be read knowing it: the time the processor takes to execute code (a task takes no simulated time, Practical 3), and the energy anything uses.

Every node in a simulation runs the same program, and each has its own copy of every module variable. The build allows up to 1,000 nodes: the option -fnesc-nido-tosnodes=1000 is on the ncc line make micaz sim runs.

What the Python script does

The script is the experimenter. It creates the simulation, decides which messages to print and where, switches motes on and off, reads their variables and runs events. The objects it uses:

Object or callWhat it is
Tossim(...)the simulation
t.getNode(1)mote 1, a Mote object
m.bootAtTime(ticks)put a boot event for the mote in the queue
t.runNextEvent()run the earliest event; True if it ran for a mote that is on
t.time(), t.timeStr()the simulated clock, as ticks or as h:m:s
m.isOn(), m.turnOff()ask whether the mote is on; switch it off
t.addChannel(name, file)send a debug channel to a file, or to sys.stdout
m.getVariable("C.v")a live view of variable v in component C on this mote

The execution log

nesC code writes to the log with four calls, all of which compile to nothing on a real mote:

CallPrints
dbg("Channel", ...)DEBUG (n): and the message, where n is the node
dbgerror("Channel", ...)ERROR (n): and the message
dbg_clear, dbgerror_clearthe message alone, for continuing a line
munotes.in61

Practical 6: Simulating a Single Mote in TOSSIM

A message goes nowhere unless the script has added its channel. TOSSIM's tutorial: "By default, a channel has no destination and messages to it are discarded." One channel can go to several places at once.

Step 1: a mote with a heartbeat

The application is one mote that beats every half second: it counts beats, toggles LED 0, drains a pretend battery by 25 per cent a beat, posts a report every second beat, and prints an error when the battery falls below 30 per cent.

$ mkdir ~/Heartbeat
$ cd ~/Heartbeat

HeartbeatC.nc:

module HeartbeatC {
  uses interface Boot;
  uses interface Timer<TMilli>;
  uses interface Leds;
}
implementation {
  uint16_t beats = 0;
  uint16_t charge = 100;     /* a pretend battery, in per cent */

  task void report() {
    dbg("Heartbeat", "%s report: %u beats so far, charge %u%%\n",
        sim_time_string(), beats, charge);
  }

  event void Boot.booted() {
    dbg("Boot", "%s booted, beats = %u\n", sim_time_string(), beats);
    call Timer.startPeriodic(512);
  }

  event void Timer.fired() {
    beats++;
    if (charge >= 25)
      charge -= 25;
    call Leds.led0Toggle();
    if (beats % 2 == 0)
      post report();
    if (charge < 30)
      dbgerror("Heartbeat", "%s charge down to %u%%\n", sim_time_string(), charge);
  }
}

HeartbeatAppC.nc and the Makefile:

configuration HeartbeatAppC {}
implementation {
  components MainC, LedsC, HeartbeatC, new TimerMilliC();
  HeartbeatC.Boot -> MainC;
  HeartbeatC.Timer -> TimerMilliC;
  HeartbeatC.Leds -> LedsC;
}
COMPONENT=HeartbeatAppC
override GCC = gcc-10
export GCC
PFLAGS += -fgnu89-inline -fsigned-char
include $(MAKERULES)
$ make micaz sim 2>&1 | tail -1
*** Successfully built micaz TOSSIM library.

Step 2: the event queue, one event at a time

The smallest possible look at the runtime: boot the mote a hundred nanoseconds in, 1,000 ticks, and run eight events, printing the clock after each. step.py:

from TOSSIM import *

t = Tossim([])
t.randomSeed(1)
m = t.getNode(1)
print "before boot: time", t.time(), "on?", m.isOn(), "event run?", t.runNextEvent()
m.bootAtTime(1000)
for n in range(1, 9):
    ran = t.runNextEvent()
    print "event", n, "at", t.timeStr(), "ran", ran, "on?", m.isOn()
$ python2 step.py
before boot: time 0 on? False event run? False
event 1 at 0:0:0.000000100 ran True on? True
event 2 at 0:0:0.000000110 ran True on? True
event 3 at 0:0:0.000000120 ran True on? True
event 4 at 0:0:0.224609475 ran True on? True
event 5 at 0:0:0.225586037 ran True on? True
event 6 at 0:0:0.449218850 ran True on? True
event 7 at 0:0:0.474609475 ran True on? True
event 8 at 0:0:0.500000100 ran True on? True

Read it from the top.

Before the boot, nothing can run. The queue is empty: runNextEvent() returns False and the clock stays at 0. A simulation does nothing until something is put in the queue, and bootAtTime is what puts the first event there.

munotes.in62

Practical 6: Simulating a Single Mote in TOSSIM

Events 1 to 3 are the boot. The boot event at 100 nanoseconds (1,000 ticks), then two tasks 10 nanoseconds apart: the tasks TinyOS and the application post while booting, such as the timer system's own task that startPeriodic posts (Practical 3 met it).

Events 4 to 7 are hardware the application never sees. Nothing in HeartbeatC happens at 0.2246 or 0.4492 seconds. They belong to the MICAz's timer chip, which TOSSIM simulates at the level of its registers (tos/chips/atm128/timer/sim/): the chip's 8-bit counter cannot count to half a second in one go, so it works towards the alarm in steps, and each step is an event. The application sees only the result, Timer.fired.

Event 8 is the first beat's alarm, at 0.5 seconds after the boot, 0.500000100.

So the "runtime environment" MU asks about is visible here as the queue itself: a clock that jumps from event to event, boot and tasks and simulated hardware all in one queue, and the application's code running only when one of its events comes up.

Step 3: the execution log

Now the mote's own messages. This script sends the Boot and Heartbeat channels to the screen, sends Heartbeat to a file as well, and runs two simulated seconds. log.py:

from TOSSIM import *
import sys

t = Tossim([])
t.randomSeed(1)
log = open("heartbeat.log", "w")
t.addChannel("Boot", sys.stdout)
t.addChannel("Heartbeat", sys.stdout)
t.addChannel("Heartbeat", log)
m = t.getNode(1)
m.bootAtTime(0)
while t.time() < 2 * t.ticksPerSecond():
    t.runNextEvent()
$ python2 log.py
DEBUG (1): 0:0:0.000000000 booted, beats = 0
DEBUG (1): 0:0:1.000000020 report: 2 beats so far, charge 50%
ERROR (1): 0:0:1.500000010 charge down to 25%
$ cat heartbeat.log
DEBUG (1): 0:0:1.000000020 report: 2 beats so far, charge 50%
ERROR (1): 0:0:1.500000010 charge down to 25%

Three lines on the screen and two in the file, and every difference has a reason.

The boot line is on the Boot channel, which went only to the screen, so it is not in the file. The Heartbeat channel went to both.

The report at 1.000000020 is two task latencies after the second beat: the timer's event is delivered from a task at 1.000000010 (Practical 3), and the report is a task posted by it. The first beat, at 0.5 seconds, posted no report, because beats % 2 was 1.

The error line is dbgerror, so it begins ERROR (1) instead of DEBUG (1); otherwise it behaves exactly like dbg, including going to both of its channel's destinations. A student reading a long log can find the problems with grep ERROR.

Nothing prints for the beats at 0.5 and 1.5 seconds except what the code chose to print. The log is not a trace of everything the mote did; it is what the programmer asked to see.

munotes.in63

Practical 6: Simulating a Single Mote in TOSSIM

Step 4: reading a variable while the mote runs

The log shows what the program chose to print. TOSSIM can also look inside it. The build wrote app.xml, a description of every component and every variable in the application, and TinyOS's Python library reads it. vars.py reads beats and charge just after each half-second beat:

from TOSSIM import *
from tinyos.tossim.TossimApp import *

n = NescApp()
t = Tossim(n.variables.variables())
t.randomSeed(1)
m = t.getNode(1)
beats = m.getVariable("HeartbeatC.beats")
charge = m.getVariable("HeartbeatC.charge")
m.bootAtTime(0)
for half in range(1, 7):
    while t.time() < half * t.ticksPerSecond() / 2 + 1000:
        t.runNextEvent()
    print t.timeStr(), "beats", beats.getData(), "charge", charge.getData()
$ python2 vars.py
Allocated variable HeartbeatC$beats
Allocated variable HeartbeatC$charge
0:0:0.550781250 beats 1 charge 75
0:0:1.050781250 beats 2 charge 50
0:0:1.550781250 beats 3 charge 25
0:0:2.050781250 beats 4 charge 0
0:0:2.550781250 beats 5 charge 0
0:0:3.050781250 beats 6 charge 0

Allocated variable HeartbeatC$beats is TOSSIM confirming that it found the variable in app.xml. Inside the generated C, nesC names a module's variable Component$variable, and the Python side asks for it as Component.variable.

The values are exactly the program's: one beat every half second, and the charge falling by 25 a beat until the if (charge >= 25) guard holds it at 0. Notice the times. Each read happens at 0.550781250 and so on, not just after the beat, because the loop stops at the first event past the half second, and the next event in the queue was one of the timer chip's own, 50.78 milliseconds later. The clock is wherever the last event left it.

Reading a variable costs the mote nothing: TOSSIM looks into the simulated memory from outside, which no real mote allows. On hardware the only way to see beats would be to send it over the radio or the serial port, which is what Practical 8 does.

Step 5: switching the mote off and on

power.py runs the mote for two seconds, switches it off, runs three more events, boots it again at three seconds and reads beats again at four:

from TOSSIM import *
from tinyos.tossim.TossimApp import *
import sys

n = NescApp()
t = Tossim(n.variables.variables())
t.randomSeed(1)
t.addChannel("Boot", sys.stdout)
m = t.getNode(1)
beats = m.getVariable("HeartbeatC.beats")
m.bootAtTime(0)
while t.time() < 2 * t.ticksPerSecond():
    t.runNextEvent()
print "at", t.timeStr(), "beats =", beats.getData()
m.turnOff()
print "turned off; on?", m.isOn()
print "next event ran?", t.runNextEvent(), "time now", t.timeStr()
print "next event ran?", t.runNextEvent(), "time now", t.timeStr()
print "next event ran?", t.runNextEvent(), "time now", t.timeStr()
m.bootAtTime(3 * t.ticksPerSecond())
while t.time() < 4 * t.ticksPerSecond():
    t.runNextEvent()
print "at", t.timeStr(), "beats =", beats.getData()
$ python2 power.py
Allocated variable HeartbeatC$beats
DEBUG (1): 0:0:0.000000000 booted, beats = 0
at 0:0:2.000000000 beats = 3
turned off; on? False
next event ran? False time now 0:0:2.000000010
next event ran? False time now 0:0:2.050781250
next event ran? False time now 0:0:2.173828125
DEBUG (1): 0:0:3.000000000 booted, beats = 0
at 0:0:4.000000000 beats = 1
munotes.in64

Practical 6: Simulating a Single Mote in TOSSIM

Four things happened, and the tutorial describes only the first.

After turnOff, the mote's queued events are discarded as they come up. Three more events ran for mote 1, at 2.000000010, 2.050781250 and 2.173828125, and each returned False: TOSSIM took it off the queue, found the mote off, and threw it away. The clock still moved to each one's time.

Nothing is left after that. The mote's periodic timer never fires again, because the event that would have rescheduled it was discarded. Step 6 depends on this.

bootAtTime switches it back on. At 3.0 seconds the boot event ran and the application booted again.

And the reboot started from nothing. beats was 3 before it was switched off, and the boot message printed beats = 0: TOSSIM re-initialises the mote's variables when it boots, as a real mote does when power returns and its RAM is set up afresh from the program. After the reboot one beat, at 3.5 seconds, made beats 1 by four seconds. Anything a mote must remember across a power failure has to be written to flash.

Step 6: the loop that never ends

Every script so far waits for the clock with while t.time() < .... Step 5 shows why that can go wrong: once a mote is off and its last events are discarded, nothing is left in the queue, and the clock only moves when an event runs. hang.py switches the mote off at one second and then waits for two:

from TOSSIM import *

t = Tossim([])
m = t.getNode(1)
m.bootAtTime(0)
while t.time() < t.ticksPerSecond():
    t.runNextEvent()
m.turnOff()
print "switched off at", t.timeStr()
while t.time() < 2 * t.ticksPerSecond():
    t.runNextEvent()
print "this line is never reached"

It is run under timeout 5, which kills it after five seconds of real time, and python2 -u makes Python print each line at once, so the first line appears before the script is killed:

$ timeout 5 python2 -u hang.py; echo "exit status $?"
switched off at 0:0:1.000000000
exit status 124

The script printed its first line and then spun until timeout killed it: exit status 124 is timeout's own code for "I had to kill it". Mote 1's last events were discarded and the queue was empty, so runNextEvent() kept returning False instantly, the clock stayed below two seconds, and the loop's condition never became false. On your own machine, without timeout, the terminal would simply hang until you pressed Ctrl-C.

munotes.in65

Practical 6: Simulating a Single Mote in TOSSIM

The loop that is safe stops when an event neither ran nor moved the clock, because that only happens when the queue is empty. safe.py:

from TOSSIM import *

t = Tossim([])
m = t.getNode(1)
m.bootAtTime(0)

def run_until(end):
    while t.time() < end:
        before = t.time()
        ran = t.runNextEvent()
        if not ran and t.time() == before:
            return False          # the queue is empty: the clock cannot move
    return True

print "reached 1 s?", run_until(t.ticksPerSecond()), "at", t.timeStr()
m.turnOff()
print "reached 2 s?", run_until(2 * t.ticksPerSecond()), "at", t.timeStr()
$ python2 safe.py
reached 1 s? True at 0:0:1.000000000
reached 2 s? False at 0:0:1.250000000

The same situation, and run_until returns False instead of spinning: after discarding the switched-off mote's events up to 1.25 seconds, an event neither ran nor moved the clock, so the queue was empty and waiting could never succeed. Every script in the rest of this book can use a loop like this whenever a mote may be switched off.

Procedure

  1. Write a one-mote application with a timer, a task, dbg and dbgerror messages on two channels, and two module variables.
  2. Run the event queue one event at a time and print the simulated clock after each.
  3. Run the mote with the Boot and Heartbeat channels sent to the screen and one of them also to a file.
  4. Load the application's description with NescApp and read HeartbeatC.beats while the simulation runs.
  5. Switch the mote off, watch its pending events being discarded, boot it again and read its variables.
  6. Show that a loop waiting for the clock never ends once no events are left, and write the loop that does.

Observations

ObservedValue
runNextEvent() before any bootFalse; the clock stayed at 0
First eight eventsboot at 100 ns, two tasks at 110 and 120 ns, four timer-chip events, the first beat's alarm at 0.500000100 s
Log, 2 simulated secondsboot line, a report at 1.000000020 s (2 beats, 50 per cent), an ERROR line at 1.500000010 s (25 per cent)
The file for the Heartbeat channelthe report and the error line only
beats and charge read from outside1 and 75 at 0.55 s, rising by one beat and falling by 25 every half second to 0
Events after turnOffthree discarded, each returning False, at 2.000000010, 2.050781250 and 2.173828125 s
After a reboot at 3 sbeats restarted at 0; 1 by 4 s
Waiting for the clock with an empty queuenever ended; killed by timeout, exit status 124
The guarded loopstopped and reported False at 1.25 s

Result

A single MICAz mote was simulated in TOSSIM and its execution observed four ways. Running the queue one event at a time showed a clock that jumps from event to event, and a queue holding the boot, tasks and events of the simulated timer chip that the application never sees. The debug log showed dbg and dbgerror messages going only to the channels the script had added, one of them to a file as well. Reading HeartbeatC.beats and HeartbeatC.charge through app.xml showed the program's state from outside while it ran. Switching the mote off showed its remaining events being discarded; rebooting it showed its variables re-initialised; and a loop that waited for the clock after the queue had emptied never ended, which a loop that stops when an event neither runs nor moves the clock avoids.

munotes.in66

Practical 6: Simulating a Single Mote in TOSSIM

Where marks are lost

Expecting output from a channel you did not add. A message on a channel with no destination is discarded. addChannel every channel you want to see.

Treating the log as a trace of everything. It shows only what the code asked to print.

Believing a task or a function takes simulated time. It takes none; only events move the clock.

Waiting for the clock with nothing in the queue. The script hangs. Stop when an event neither runs nor moves the clock.

Writing getVariable("beats"). It is Component.variable: "HeartbeatC.beats". And Tossim must be created with NescApp()'s variables for it to work.

Expecting a variable to survive a reboot. RAM is set up afresh at boot; beats came back as 0.

Forgetting that runNextEvent() returning False does not mean the queue is empty. For a switched-off mote it means an event was discarded; the clock may still have moved.

For the journal

Aim; what a discrete-event simulator is, and what TOSSIM does and does not simulate; the table of Python calls; the table of dbg calls; the four files and the Makefile; each of the six scripts with its output; an explanation of the first eight events; the observation table; the result.

Quick revision

  • TOSSIM is a discrete-event simulator: a queue of timed events, and a clock that jumps from one to the next.
  • 10,000,000,000 ticks a second; timeStr() prints h:m:s.
  • bootAtTime puts the first event in the queue; nothing runs before it.
  • The queue holds the boot, tasks, and simulated hardware such as the timer chip.
  • Code takes no simulated time; energy is not simulated.
  • dbg prints DEBUG (n), dbgerror prints ERROR (n); a channel prints only where addChannel sends it.
  • NescApp() reads app.xml; m.getVariable("Component.variable").getData() reads a live variable.
  • turnOff(): queued events are discarded as they come up; a reboot re-initialises the variables.
  • With an empty queue the clock never moves: a while t.time() < end loop hangs.
  • Up to 1,000 nodes, from -fnesc-nido-tosnodes=1000 in the build.
munotes.in67

Practical 6: Simulating a Single Mote in TOSSIM

Questions you must be able to answer

1. What is a discrete-event simulator? One that keeps a queue of events stamped with the times they occur and runs them in time order, moving its clock straight to each one. Nothing happens between events.

2. What does runNextEvent() return? True if it ran an event for a mote that is on; False if the queue was empty, or if the event it took was for a switched-off mote and was discarded.

3. The queue held events at 0.2246 and 0.4492 seconds that your program never mentions. What are they? The simulated MICAz timer chip. TOSSIM simulates it at the level of its registers, and its 8-bit counter reaches the half-second alarm in steps, each of which is an event.

4. What is the difference between dbg and dbgerror? Only the prefix: DEBUG (n) against ERROR (n). Both go to the destinations added for their channel.

5. How did the script read beats without the program printing it? Through app.xml, which the build writes: NescApp() reads it, Tossim is created with its variable list, and getVariable("HeartbeatC.beats").getData() reads the simulated memory.

6. What happened to beats when the mote was switched off and booted again? It came back as 0. The reboot re-initialised the variables, as a real mote's RAM is set up afresh when power returns.

7. Why did hang.py never finish, and how do you prevent it? After the mote was switched off its last events were discarded and the queue was empty, so the clock could not move and the loop's condition stayed true for ever. Stop the loop when an event neither runs nor moves the clock, as run_until in safe.py does.

8. What can TOSSIM not tell you about a mote? How long its code takes to run, and how much energy it uses. It simulates the order of events, not the processor's time or power.

Contents This chapter on its own page

munotes.in68

Chapter Nine

Practical 7: Mote-to-Mote Radio Communication: Signal Strength and Packet Loss

Syllabus topic Module 1, "Mote-to-Mote Radio Communication Simulation: Simulate packet transmission between two motes and analyze signal strength and packet loss under varying radio gain values."

Aim

To simulate packet transmission between two motes, and to analyse the signal strength and the packet loss as the radio gain between them is varied.

What you need to know before you start

Decibels, in one paragraph

Radio power is measured in dBm: decibels relative to one milliwatt. 0 dBm is 1 milliwatt, and every 10 dB down is a tenth as much, so minus 90 dBm is a thousand-millionth of a milliwatt. Because radio signals weaken by factors of millions over short distances, decibels turn those factors into subtractions. The CC2420 transmits at up to 0 dBm (Practical 1); a MICAz can decode a signal as weak as about minus 94 dBm, the "typical" receive sensitivity its datasheet gives.

What TOSSIM models

Gain. Each directed link in TOSSIM has a gain: how many decibels the signal loses on the way, set by the script with r.add(source, destination, gain). It stands for distance, walls and antennas together. TOSSIM transmits at 0 dBm (TossimPacketModelC.nc, line 270), so the received power is simply the gain: a link of minus 80 dB delivers minus 80 dBm.

Noise. Every receiver also hears noise: other radios, electronics, the environment. TOSSIM does not assume a steady noise level. It takes a noise trace, a list of readings recorded by a real mote, and builds a statistical model from it with an algorithm called Closest Pattern Matching (CPM), which, in the words of TinyOS's tutorial, "can capture bursts of interference and other correlated phenomena". TinyOS ships traces; meyer-heavy.txt is, the tutorial says, "a noise trace taken from Meyer Library at Stanford University". The tutorial also says the trace fed to CPM "must be at least 100 entries long", which is why every script in this book feeds it the first 100.

Signal-to-noise ratio. What decides whether a packet survives is not the signal alone but how far it stands above the noise: the SNR, signal minus noise in dB. A packet at minus 90 dBm over noise at minus 98 has an SNR of 8 dB.

Signal strength. A real CC2420 measures the power it is receiving and reports it as the RSSI, received signal strength indicator. Its datasheet gives the conversion as P = RSSI_VAL + RSSI_OFFSET dBm, with the offset "approximately -45", averaged over 128 microseconds, accurate to about 6 dB. TOSSIM reports the equivalent through its TossimPacket interface: strength(msg) is the power a received packet arrived with, signal plus noise, in dBm.

Step 1: the application

One mote sends a hundred numbered packets, one every 100 binary milliseconds; the other logs each one it receives with its strength. Make a folder:

$ mkdir ~/RadioLink
$ cd ~/RadioLink
munotes.in69

Practical 7: Mote-to-Mote Radio Communication: Signal Strength and Packet Loss

RadioLink.h:

#ifndef RADIO_LINK_H
#define RADIO_LINK_H
enum {
  AM_LINK = 7,
  SENDER = 1,
  RECEIVER = 2,
  PACKETS = 100,       /* how many packets the sender sends */
  INTERVAL = 100       /* one every 100 binary milliseconds */
};
typedef nx_struct link_msg {
  nx_uint16_t seq;     /* 0, 1, 2 ... so a loss can be pinned to a packet */
} link_msg_t;
#endif

RadioLinkC.nc:

#include "RadioLink.h"

module RadioLinkC {
  uses interface Boot;
  uses interface Timer<TMilli>;
  uses interface SplitControl as RadioControl;
  uses interface AMSend;
  uses interface Receive;
  uses interface TossimPacket;
}
implementation {
  message_t packet;
  bool busy = FALSE;
  uint16_t sent = 0;

  event void Boot.booted() {
    call RadioControl.start();
  }

  event void RadioControl.startDone(error_t err) {
    if (TOS_NODE_ID == SENDER)
      call Timer.startPeriodic(INTERVAL);
  }

  event void RadioControl.stopDone(error_t err) {}

  event void Timer.fired() {
    link_msg_t* m;
    if (sent == PACKETS) {
      call Timer.stop();
      return;
    }
    if (busy)
      return;
    m = (link_msg_t*) call AMSend.getPayload(&packet, sizeof(link_msg_t));
    m->seq = sent;
    if (call AMSend.send(RECEIVER, &packet, sizeof(link_msg_t)) == SUCCESS) {
      busy = TRUE;
      sent++;
      dbg("Link", "sent %u\n", m->seq);
    }
  }

  event void AMSend.sendDone(message_t* msg, error_t err) {
    busy = FALSE;
  }

  event message_t* Receive.receive(message_t* msg, void* payload, uint8_t len) {
    link_msg_t* m = (link_msg_t*) payload;
    dbg("Link", "received %u strength %d\n", m->seq, call TossimPacket.strength(msg));
    return msg;
  }
}

The sender counts what it sends; the receiver prints the sequence number and the strength of everything it receives. Both print on one channel, Link, so the script can count sent against received from a single log.

RadioLinkAppC.nc. TossimPacket is provided by TossimActiveMessageC, TOSSIM's own active-message component, which is wired in directly because TOSSIM's ActiveMessageC does not pass that interface on:

#include "RadioLink.h"

configuration RadioLinkAppC {}
implementation {
  components MainC, RadioLinkC as App, new TimerMilliC();
  components ActiveMessageC, TossimActiveMessageC;
  components new AMSenderC(AM_LINK), new AMReceiverC(AM_LINK);

  App.Boot -> MainC;
  App.Timer -> TimerMilliC;
  App.RadioControl -> ActiveMessageC;
  App.AMSend -> AMSenderC;
  App.Receive -> AMReceiverC;
  App.TossimPacket -> TossimActiveMessageC;
}
COMPONENT=RadioLinkAppC
override GCC = gcc-10
export GCC
PFLAGS += -fgnu89-inline -fsigned-char
include $(MAKERULES)
$ make micaz sim 2>&1 | tail -1
*** Successfully built micaz TOSSIM library.

Step 2: a script that takes the gain as an argument

run.py sets one gain in both directions, gives both motes the noise model from the trace named on the command line, runs twelve simulated seconds (the hundred packets take ten), and then reads its own log back to count:

from TOSSIM import *
import sys

gain = float(sys.argv[1])
t = Tossim([])
t.randomSeed(1)
r = t.radio()
r.add(1, 2, gain)          # sender to receiver
r.add(2, 1, gain)          # and back
noise = open(sys.argv[2]).readlines()[:100]
for n in (1, 2):
    m = t.getNode(n)
    for line in noise:
        m.addNoiseTraceReading(int(line))
    m.createNoiseModel()
    m.bootAtTime(n * 1000)

log = open("link.log", "w")
t.addChannel("Link", log)
while t.time() < 12 * t.ticksPerSecond():
    t.runNextEvent()
log.close()

sent = received = 0
strengths = []
for line in open("link.log"):
    words = line.split()
    if words[2] == "sent":
        sent += 1
    elif words[2] == "received":
        received += 1
        strengths.append(int(words[-1]))
loss = 100.0 * (sent - received) / sent
if strengths:
    s = "strength %d to %d, mean %.1f dBm" % (min(strengths), max(strengths),
                                              float(sum(strengths)) / len(strengths))
else:
    s = "nothing received"
print "gain %4d dB: sent %d, received %3d, loss %5.1f %%, %s" % (gain, sent, received, loss, s)
munotes.in70

Practical 7: Mote-to-Mote Radio Communication: Signal Strength and Packet Loss

Step 3: the curve that decides every packet

When a packet arrives, TOSSIM works out its SNR and then tosses a coin weighted by a packet reception rate for that SNR. The curve is in CpmModelC.nc, and it is short enough to read whole:

$ sed -n '/double prr_estimate_from_snr/,/^  }/p' $TOSDIR/lib/tossim/CpmModelC.nc
  double prr_estimate_from_snr(double SNR) {
    // Based on CC2420 measurement by Kannan.
    // The updated function below fixes the problem of non-zero PRR
    // at very low SNR. With this function PRR is 0 for SNR <= 3.
    double beta1 = 0.9794;
    double beta2 = 2.3851;
    double X = SNR-beta2;
    double PSE = 0.5*erfc(beta1*X/sqrt(2));
    double prr_hat = pow(1-PSE, 23*2);
    dbg("CpmModelC,SNR", "SNR is %lf, PRR is %lf\n", SNR, prr_hat);
    if (prr_hat > 1)
      prr_hat = 1.1;
    else if (prr_hat < 0)
      prr_hat = -0.1;

    return prr_hat;
  }

Its comment says where it comes from: a fit to measurements of the CC2420. PSE is the chance that one symbol is received wrongly; the CC2420 sends each byte as two 4-bit symbols (its datasheet, section 12), and the curve treats a packet as 23 bytes, 46 symbols, all of which must be right: (1 - PSE) raised to the power 46. This short program, prr.py, computes the same formula for SNRs from 0 to 10 dB, in Python 3 on the same Ubuntu:

# TOSSIM's packet reception curve (CpmModelC.nc, prr_estimate_from_snr),
# computed for signal-to-noise ratios from 0 to 10 dB.
from math import erfc, sqrt

BETA1, BETA2 = 0.9794, 2.3851
for snr in range(0, 11):
    x = snr - BETA2
    pse = 0.5 * erfc(BETA1 * x / sqrt(2))     # chance one symbol is wrong
    prr = (1 - pse) ** (23 * 2)               # all 46 symbols right
    print("SNR %2d dB: symbol error %.6f, packet received %.4f" % (snr, pse, prr))
$ python3 prr.py
SNR  0 dB: symbol error 0.990254, packet received 0.0000
SNR  1 dB: symbol error 0.912541, packet received 0.0000
SNR  2 dB: symbol error 0.646975, packet received 0.0000
SNR  3 dB: symbol error 0.273510, packet received 0.0000
SNR  4 dB: symbol error 0.056867, packet received 0.0677
SNR  5 dB: symbol error 0.005218, packet received 0.7861
SNR  6 dB: symbol error 0.000200, packet received 0.9909
SNR  7 dB: symbol error 0.000003, packet received 0.9999
SNR  8 dB: symbol error 0.000000, packet received 1.0000
SNR  9 dB: symbol error 0.000000, packet received 1.0000
SNR 10 dB: symbol error 0.000000, packet received 1.0000
munotes.in71

Practical 7: Mote-to-Mote Radio Communication: Signal Strength and Packet Loss

The whole behaviour of a link is in this table. Below 4 dB of SNR a packet essentially never survives, above 6 dB it essentially always does, and between them, in two decibels, the reception rate goes from 7 per cent through 79 per cent to 99 per cent. The comment in the source says the same thing from the other side: "PRR is 0 for SNR <= 3". Because the curve is so steep, a link is usually either good or dead, and the interesting links are the few decibels in between.

Step 4: the sweep in a quiet room

First with the quiet trace. Its first hundred readings, counted:

$ head -100 $TOSDIR/lib/tossim/noise/casino-lab.txt | sort -n | uniq -c
      2 -99
     68 -98
     29 -97
      1 -96

Every reading is between minus 99 and minus 96 dBm: a steady noise floor with no bursts. Now eight gains:

$ for g in -80 -88 -90 -91 -92 -93 -94 -95; do python2 run.py $g $TOSDIR/lib/tossim/noise/casino-lab.txt; done
gain  -80 dB: sent 100, received 100, loss   0.0 %, strength -80 to -80, mean -80.0 dBm
gain  -88 dB: sent 100, received 100, loss   0.0 %, strength -88 to -88, mean -88.0 dBm
gain  -90 dB: sent 100, received 100, loss   0.0 %, strength -90 to -90, mean -90.0 dBm
gain  -91 dB: sent 100, received 100, loss   0.0 %, strength -91 to -91, mean -91.0 dBm
gain  -92 dB: sent 100, received  92, loss   8.0 %, strength -92 to -91, mean -91.7 dBm
gain  -93 dB: sent 100, received  37, loss  63.0 %, strength -93 to -92, mean -92.0 dBm
gain  -94 dB: sent 100, received   0, loss 100.0 %, nothing received
gain  -95 dB: sent 100, received   0, loss 100.0 %, nothing received

With a steady floor of about minus 98 dBm, the link is perfect down to minus 91 dB and dead at minus 94, and the whole fall happens in three decibels: 8 per cent lost at minus 92, 63 per cent at minus 93, all of it at minus 94. That is step 3's curve seen from outside: minus 91 dB is about 7 dB above the floor, minus 93 is about 5, minus 94 about 4.

The strength column is the gain. A packet sent at 0 dBm over a minus 80 dB link arrives at minus 80 dBm, and the receiver reports exactly that. Near the cliff the mean creeps above the gain (minus 91.7 at a gain of minus 92) because strength is signal plus noise, and the noise is then only a few decibels below the signal. On a real CC2420 the same figure is the RSSI, give or take its 6 dB accuracy.

munotes.in72

Practical 7: Mote-to-Mote Radio Communication: Signal Strength and Packet Loss

Step 5: the sweep in a noisy library

The Meyer Library trace's first hundred readings:

$ head -100 $TOSDIR/lib/tossim/noise/meyer-heavy.txt | sort -n | uniq -c
      6 -99
     62 -98
      4 -97
      3 -96
      2 -94
      1 -93
      2 -91
      2 -90
      2 -87
      3 -86
      5 -82
      4 -81
      1 -78
      1 -64
      1 -49
      1 -39

Most readings are the same quiet floor, minus 98. But about a quarter are louder, some much louder: bursts at minus 82, minus 64, minus 49, even minus 39. The same sweep, over a wider range:

$ for g in -50 -60 -70 -75 -80 -85 -88 -90 -92 -93 -94 -95; do python2 run.py $g $TOSDIR/lib/tossim/noise/meyer-heavy.txt; done
gain  -50 dB: sent 100, received 100, loss   0.0 %, strength -50 to -50, mean -50.0 dBm
gain  -60 dB: sent 100, received  99, loss   1.0 %, strength -60 to -60, mean -60.0 dBm
gain  -70 dB: sent 100, received  99, loss   1.0 %, strength -70 to -70, mean -70.0 dBm
gain  -75 dB: sent 100, received  98, loss   2.0 %, strength -75 to -75, mean -75.0 dBm
gain  -80 dB: sent 100, received  87, loss  13.0 %, strength -80 to -80, mean -80.0 dBm
gain  -85 dB: sent 100, received  79, loss  21.0 %, strength -85 to -84, mean -85.0 dBm
gain  -88 dB: sent 100, received  72, loss  28.0 %, strength -88 to -87, mean -88.0 dBm
gain  -90 dB: sent 100, received  71, loss  29.0 %, strength -90 to -90, mean -90.0 dBm
gain  -92 dB: sent 100, received  63, loss  37.0 %, strength -92 to -91, mean -92.0 dBm
gain  -93 dB: sent 100, received  44, loss  56.0 %, strength -93 to -92, mean -92.2 dBm
gain  -94 dB: sent 100, received   4, loss  96.0 %, strength -93 to -93, mean -93.0 dBm
gain  -95 dB: sent 100, received   0, loss 100.0 %, nothing received

The same mote pair, the same gains, and a completely different link. The noisy library loses packets at gains where the quiet room lost none: 13 per cent at minus 80 dB, 21 at minus 85, 29 at minus 90. The cliff is still there, at the same place, because the library's usual floor is the same minus 98. What is new is a slope before the cliff, and the next command shows where it comes from.

If loss is caused by noise bursts that come within a few decibels of the signal, the share of noise readings at least that loud should roughly predict it. Step 3's curve drops from 0.78 to 0.07 between 5 and 4 dB of SNR, so count, for each gain, the readings louder than the gain minus 4 dB:

munotes.in73

Practical 7: Mote-to-Mote Radio Communication: Signal Strength and Packet Loss

$ for g in -70 -80 -85 -90 -92; do head -100 $TOSDIR/lib/tossim/noise/meyer-heavy.txt | awk -v g=$g '$1 > g - 4 {n++} END {printf "gain %d: %d of 100 readings within 4 dB of the signal or louder\n", g, n}'; done
gain -70: 3 of 100 readings within 4 dB of the signal or louder
gain -80: 13 of 100 readings within 4 dB of the signal or louder
gain -85: 18 of 100 readings within 4 dB of the signal or louder
gain -90: 23 of 100 readings within 4 dB of the signal or louder
gain -92: 25 of 100 readings within 4 dB of the signal or louder

The count tracks the measured loss: 13 readings against 13 per cent lost at minus 80 dB, 18 against 21 at minus 85, 23 against 29 at minus 90. The slope is the noise bursts. Whenever a burst of interference comes within a few decibels of the signal, the packet in the air at that moment is lost, however good the link is the rest of the time. The count is a rough predictor and falls behind nearer the cliff, because there even the ordinary floor starts to cost packets (step 3's curve at 5 and 6 dB), and because CPM reproduces the trace's patterns, not a copy of its hundred readings.

Packet loss against link gain for the same two motes, with a quiet noise trace and a noisy one, drawn from the two sweeps above.

Figure 9.1 Loss against gain: a cliff in a quiet room, a slope and a cliff in a noisy one

The figure puts the two sweeps together, and it is the answer to what MU asks. Signal strength sets where a link dies; the noise environment sets how it degrades on the way.

Step 6: which packets were lost

Run one weak link in the noisy library, minus 92 dB, and list the sequence numbers that never arrived. losses.awk reads the log, collects what was received, and prints what was not, with the longest run of consecutive losses:

$3 == "received" { got[$4] = 1 }
END {
    run = longest = 0
    for (i = 0; i < 100; i++) {
        if (i in got) { run = 0; continue }
        printf "%d ", i
        run++
        if (run > longest) longest = run
    }
    printf "\nlongest run of consecutive losses: %d\n", longest
}
$ python2 run.py -92 $TOSDIR/lib/tossim/noise/meyer-heavy.txt
gain  -92 dB: sent 100, received  63, loss  37.0 %, strength -92 to -91, mean -92.0 dBm
$ awk -f losses.awk link.log
1 5 8 10 15 17 20 23 24 26 28 32 35 40 41 44 45 47 49 53 55 56 57 58 60 62 65 67 71 73 78 79 82 88 91 94 97
longest run of consecutive losses: 4
munotes.in74

Practical 7: Mote-to-Mote Radio Communication: Signal Strength and Packet Loss

Thirty-seven packets lost, and they are spread through the whole run rather than bunched at one end. Most losses are single packets; a few come in pairs (23 and 24, 40 and 41, 78 and 79), and the longest run is four, 55 to 58, which is a burst of interference lasting about four tenths of a second. That is what CPM exists to reproduce: real interference comes in bursts, so losses are not independent coin tosses, and a protocol that retries immediately after a loss is more likely to lose the retry too.

Procedure

  1. Write a two-mote application: the sender sends 100 numbered packets; the receiver logs each packet's number and its strength from TossimPacket.strength.
  2. Write a script that takes the link gain and a noise trace as arguments, runs the simulation, and counts packets sent and received from the log.
  3. Print TOSSIM's reception curve from CpmModelC.nc and compute it for SNRs from 0 to 10 dB.
  4. Sweep the gain from minus 80 to minus 95 dB with the quiet trace casino-lab.txt.
  5. Sweep it from minus 50 to minus 100 dB with the noisy trace meyer-heavy.txt.
  6. List which packets were lost on one weak link and look for bursts.

Observations

Link gain, dBLoss, quiet roomLoss, noisy library
-500 %
-601 %
-701 %
-752 %
-800 %13 %
-8521 %
-880 %28 %
-900 %29 %
-910 %
-928 %37 %
-9363 %56 %
-94100 %96 %
-95100 %100 %
MeasuredValue
Received strengthequal to the gain, since TOSSIM transmits at 0 dBm; slightly above it near the cliff, where noise adds to it
Reception rate against SNR0 at 3 dB, 0.068 at 4 dB, 0.786 at 5 dB, 0.991 at 6 dB
Noise readings within 4 dB of the signal, noisy trace13 at -80 dB, 18 at -85, 23 at -90, 25 at -92
Losses at -92 dB, noisy trace37 of 100, spread through the run, longest run 4

Result

Two motes were simulated in TOSSIM, one sending 100 numbered packets to the other, with the link gain varied from minus 50 to minus 100 dB under a quiet and a noisy recorded noise trace. The received signal strength equalled the gain, because TOSSIM transmits at 0 dBm. In the quiet trace the link lost nothing down to minus 91 dB and everything from minus 94 dB, a cliff of three decibels that follows TOSSIM's reception curve, which rises from 7 to 99 per cent between 4 and 6 dB of signal-to-noise ratio. In the noisy trace the same link lost packets from minus 60 dB onwards, 13 per cent at minus 80 and 29 per cent at minus 90, because bursts of interference came within a few decibels of the signal, and the share of noise readings that loud tracked the measured loss. Packet loss is set by the signal-to-noise ratio: the gain sets where the link fails, and the burstiness of the noise sets how it degrades before that.

munotes.in75

Practical 7: Mote-to-Mote Radio Communication: Signal Strength and Packet Loss

Where marks are lost

Treating packet loss as a function of distance alone. The same gain lost 0 per cent in one room and 13 in another. The noise decides as much as the signal.

Reading strength as the signal only. It is signal plus noise, which is why it rises above the gain near the cliff.

Too short a noise trace. CPM needs at least 100 readings, or it cannot build its model.

Setting the gain in one direction only. r.add(1, 2, g) makes a link from 1 to 2 and nothing back. Acknowledgements and replies need the other direction too.

One run per gain, reported as a law. A different seed moves each figure by a few packets. Say the run was seeded, and look at the trend, not the third digit.

Quoting losses without the noise trace used. The environment is part of the result.

Forgetting that dB and dBm are different. Gain is a ratio (dB); received power is an absolute level (dBm). TOSSIM's 0 dBm transmitter makes them equal numbers, which is a convenience, not an identity.

For the journal

Aim; dB and dBm, gain, noise, SNR and RSSI, in a few lines each; the four files, the Makefile and the script; the reception-curve source and prr.py with its table; the two noise traces' readings; both sweeps; the burst count; the figure or a hand-drawn graph of loss against gain for both traces; the lost packets at minus 92 dB; the observation tables; the result.

Quick revision

  • TOSSIM transmits at 0 dBm, so received power equals the link gain.
  • r.add(src, dst, gain) sets one direction of one link.
  • Noise comes from a recorded trace through Closest Pattern Matching, which keeps its bursts; at least 100 readings.
  • A packet survives or not by a coin weighted by its SNR: 0 at 3 dB, 7 per cent at 4, 79 at 5, 99 at 6.
  • Quiet room: perfect to -91 dB, dead at -94.
  • Noisy library: losses from -60 dB, 13 per cent at -80, 29 at -90, then the same cliff.
  • The slope is interference bursts; the cliff is the noise floor.
  • TossimPacket.strength(msg) is TOSSIM's RSSI: signal plus noise, in dBm.
  • CC2420 RSSI: P = RSSI_VAL + RSSI_OFFSET, offset about -45, accuracy 6 dB.
  • Losses come in short bursts, not independently.
munotes.in76

Practical 7: Mote-to-Mote Radio Communication: Signal Strength and Packet Loss

Questions you must be able to answer

1. What is a link gain in TOSSIM, and what does it stand for? The number of decibels a signal loses from one mote to another, set per direction with r.add. It stands for distance, obstacles and antennas together.

2. Why did the received strength equal the gain? Because TOSSIM transmits at 0 dBm, and received power is transmit power plus gain.

3. What decides whether one packet is received? Its signal-to-noise ratio: TOSSIM computes a reception probability from the SNR with a curve fitted to CC2420 measurements and tosses a coin weighted by it.

4. Why is the drop from perfect to dead so steep in the quiet trace? Because the reception curve rises from 7 to 99 per cent in 2 dB of SNR, and the quiet trace's noise barely varies, so each decibel of gain is almost a decibel of SNR.

5. Why did the noisy library lose 13 per cent at minus 80 dB, where the quiet room lost nothing? Its noise has bursts. About 13 in 100 of its readings come within 4 dB of a minus 80 dBm signal, and a packet in the air during such a burst is lost.

6. What is the RSSI on a real CC2420? The chip's measurement of received power, averaged over 128 microseconds: P = RSSI_VAL + RSSI_OFFSET dBm, with the offset about minus 45, accurate to about 6 dB.

7. The losses at minus 92 dB came in runs of up to four. Why does that matter? It shows the losses are correlated, as real interference is. A sender that retries at once after a loss is likely to meet the same burst.

8. How would you make this link more reliable without moving the motes? Raise the SNR: transmit at more power where the radio allows it, or move the channel away from the interference; or add acknowledgements and retries spaced out in time, so that a retry misses the burst that killed the first attempt.

Contents This chapter on its own page

munotes.in77

Chapter Ten

Practical 8: Mote-to-PC Serial Communication Through the SerialForwarder

Syllabus topic Module 1, "Mote-to-PC Serial Communication Simulation: Implement serial communication between a sensor mote and PC to monitor real-time data transmission."

Aim

To implement serial communication between a sensor mote and a PC, and to monitor the data the mote sends as it arrives.

What you need to know before you start

Why a mote talks to a PC

A sensor network's data is only useful once it leaves the network. Practical 2's base station collected readings; this practical is the last step, getting them off the mote and into a PC. A real mote does it through a serial port: the MICAz sits on a programming board with a USB or serial connection, and the TelosB has USB built in. On the PC the mote appears as a port such as /dev/ttyUSB0 on Linux or COM3 on Windows.

The serial stack

TinyOS sends a packet over the serial line the same way it sends one over the radio: with active messages, each carrying a destination, a source, a length, a group and an AM type, followed by the payload. The components are SerialActiveMessageC, to start the serial stack, and SerialAMSenderC and SerialAMReceiverC, which work exactly like AMSenderC and AMReceiverC. A program that sends over the radio can send over the serial port by rewiring two lines.

On the wire, TEP 113 (TinyOS's design document for serial communication) wraps each packet in a frame: a delimiter byte 0x7e at each end, a protocol byte, a sequence number, a dispatch byte saying what kind of packet follows, the packet, and a two-byte CRC, with any 0x7e or 0x7d inside escaped. The PC side of TinyOS handles all of that framing for you.

The SerialForwarder

Only one program at a time can own a serial port. TinyOS therefore puts a small server between the port and the programs that want its data: the SerialForwarder. It reads packets from the mote and sends each one to every program connected to it over a network socket, and it sends their packets to the mote. Programs talk to it, not to the port, so several can watch one mote at once, and the mote can be on a different machine.

TOSSIM has its own SerialForwarder built in. When an application is built with make micaz sim-sf instead of make micaz sim, the simulated mote's serial port is connected to a SerialForwarder on a TCP port of your choice, and every PC tool that works with a real mote works, unchanged, with the simulated one.

Where "-comm" points

Every TinyOS PC tool takes a -comm argument naming its packet source. The tutorial lists the forms:

SourceWhat it connects to
sf@localhost:9002a SerialForwarder on this machine, port 9002 (what this practical uses)
serial@/dev/ttyUSB0:micaza MICAz on a USB serial port, at the MICAz's 57600 baud
serial@/dev/ttyUSB0:telosba TelosB, at 115200 baud
serial@COM3:57600a Windows port, with the speed given as a number
munotes.in78

Practical 8: Mote-to-PC Serial Communication Through the SerialForwarder

With no -comm at all, a tool reads the environment variable MOTECOM, and if that is not set either it connects to a SerialForwarder on the default port.

Step 1: a mote that reports over the serial port

The mote reads a sensor every half second and sends each reading, numbered, to the PC. The sensor is TinyOS's own SineSensorC, a test component whose readings follow a sine wave, so the PC has something that changes to monitor.

$ mkdir ~/SerialReporter
$ cd ~/SerialReporter

SerialReporter.h:

#ifndef SERIAL_REPORTER_H
#define SERIAL_REPORTER_H
enum {
  AM_READING_MSG = 10     /* the message type the PC will look for */
};
typedef nx_struct reading_msg {
  nx_uint16_t seq;        /* 0, 1, 2 ... */
  nx_uint16_t reading;    /* the sensor's value */
} reading_msg_t;
#endif

SerialReporterC.nc:

#include "SerialReporter.h"

module SerialReporterC {
  uses interface Boot;
  uses interface Timer<TMilli>;
  uses interface SplitControl as SerialControl;
  uses interface AMSend;
  uses interface Read<uint16_t>;
  uses interface Leds;
}
implementation {
  message_t packet;
  bool busy = FALSE;
  uint16_t seq = 0;

  event void Boot.booted() {
    call SerialControl.start();
  }

  event void SerialControl.startDone(error_t err) {
    call Timer.startPeriodic(512);
  }

  event void SerialControl.stopDone(error_t err) {}

  event void Timer.fired() {
    call Read.read();
  }

  event void Read.readDone(error_t result, uint16_t value) {
    reading_msg_t* m;
    if (busy)
      return;
    m = (reading_msg_t*) call AMSend.getPayload(&packet, sizeof(reading_msg_t));
    m->seq = seq;
    m->reading = value;
    if (call AMSend.send(AM_BROADCAST_ADDR, &packet, sizeof(reading_msg_t)) == SUCCESS) {
      busy = TRUE;
      seq++;
      call Leds.led0Toggle();
    }
  }

  event void AMSend.sendDone(message_t* msg, error_t err) {
    busy = FALSE;
  }
}

It is Practical 2's sensor mote with the radio replaced by the serial port: start the serial stack (split-phase, start and startDone), and on every timer event read the sensor (split-phase, read and readDone) and send the reading (split-phase again, send and sendDone). Over a serial line the destination address is not used for routing, and AM_BROADCAST_ADDR is the usual choice.

SerialReporterAppC.nc wires the module to the serial stack instead of the radio:

#include "SerialReporter.h"

configuration SerialReporterAppC {}
implementation {
  components MainC, LedsC, SerialReporterC as App, new TimerMilliC();
  components SerialActiveMessageC, new SerialAMSenderC(AM_READING_MSG);
  components new SineSensorC() as Sensor;

  App.Boot -> MainC;
  App.Timer -> TimerMilliC;
  App.Leds -> LedsC;
  App.SerialControl -> SerialActiveMessageC;
  App.AMSend -> SerialAMSenderC;
  App.Read -> Sensor;
}
COMPONENT=SerialReporterAppC
override GCC = gcc-10
export GCC
PFLAGS += -fgnu89-inline -fsigned-char
include $(MAKERULES)

Build it with the SerialForwarder included, sim-sf, not sim:

$ make micaz sim-sf 2>&1 | tail -1
*** Successfully built micaz TOSSIM library with TOSSIM Live extensions.

Step 2: the simulation, with a SerialForwarder and a throttle

sf.py runs the mote with a SerialForwarder on port 9002. A normal TOSSIM run finishes a minute of simulated time in a fraction of a second, which is no use to a person watching data arrive, so a Throttle holds the simulation back to the pace of the real clock. The mote boots three seconds in, which gives a PC program time to connect before the first reading:

munotes.in79

Practical 8: Mote-to-PC Serial Communication Through the SerialForwarder

from TOSSIM import *
import sys

t = Tossim([])
t.randomSeed(1)
sf = SerialForwarder(9002)          # the PC side connects to this TCP port
throttle = Throttle(t, 10)          # keep simulated time from running ahead of the real clock
m = t.getNode(1)
m.bootAtTime(3 * t.ticksPerSecond())   # three seconds for a client to connect
end = int(sys.argv[1]) * t.ticksPerSecond()
sf.process()
throttle.initialize()
while t.time() < end:
    throttle.checkThrottle()
    t.runNextEvent()
    sf.process()

sf.process() lets the SerialForwarder accept connections and pass packets on; it is called after every event so nothing waits. The argument is how many simulated seconds to run.

In the laboratory you run this in one terminal and the PC program in a second:

$ python2 sf.py 7

The transcripts below are one session, so the simulation is started in the background by a small script instead, with a time limit so that it cannot outlive the practical. serve.sh:

#!/bin/bash
# Start the simulation in the background, as a second terminal would,
# with a time limit so that nothing outlives the practical.
setsid timeout 60 python2 sf.py "$1" > sim.log 2>&1 < /dev/null &
sleep 1

Step 3: watching raw packets with Listen

Listen is TinyOS's simplest PC tool: it prints every packet it receives, byte by byte, in hexadecimal.

$ bash serve.sh 7
$ timeout 30 java net.tinyos.tools.Listen -comm sf@localhost:9002
00 FF FF 00 01 04 00 0A 00 00 80 00
00 FF FF 00 01 04 00 0A 00 01 86 65
00 FF FF 00 01 04 00 0A 00 02 8C C7
00 FF FF 00 01 04 00 0A 00 03 93 20
00 FF FF 00 01 04 00 0A 00 04 99 6D
00 FF FF 00 01 04 00 0A 00 05 9F AA
00 FF FF 00 01 04 00 0A 00 06 A5 D3
Error on sf@localhost:9002: java.io.IOException: end-of-stream

Seven lines, one per reading. The mote booted at three simulated seconds and read every half second from 3.5 onwards, so a seven-second run gives readings at 3.5, 4.0, 4.5, 5.0, 5.5, 6.0 and 6.5: seven. The last line is not a fault in the mote. When sf.py reached seven seconds it ended, the SerialForwarder closed its socket, and Listen reported the end of the stream.

Every line has the same shape: the same eight bytes, then two bytes that count up (00 00, 00 01 ... the sequence number), then two that change (the reading). Step 5 decodes them.

munotes.in80

Practical 8: Mote-to-PC Serial Communication Through the SerialForwarder

Step 4: a monitoring program of our own

Listen proves the data arrives. A monitoring program has to understand it. The SerialForwarder's protocol is simple enough to speak directly, and TinyOS's own Python library shows it in a few lines (support/sdk/python/tinyos/packet/SFProtocol.py): the client sends the two characters U and space, the forwarder answers with the same two, and from then on every packet comes as one byte giving its length followed by that many bytes. This program, reader.py, does exactly that in Python 3, decodes each packet, and prints the reading with the time it arrived:

# reader.py: a PC program that reads a mote's readings from a SerialForwarder.
import socket
import struct
import time

def read_exact(sock, n):
    data = b""
    while len(data) < n:
        chunk = sock.recv(n - len(data))
        if not chunk:
            return None                  # the forwarder closed the connection
        data += chunk
    return data

sock = socket.create_connection(("localhost", 9002))
sock.sendall(b"U ")                      # the SerialForwarder handshake
assert read_exact(sock, 2) == b"U "
start = time.time()
while True:
    size = read_exact(sock, 1)
    if size is None:
        break
    packet = read_exact(sock, size[0])
    dispatch, dest, src, length, group, am_type = struct.unpack(">BHHBBB", packet[:8])
    seq, reading = struct.unpack(">HH", packet[8:8 + length])
    print("%5.1f s  type %d  from %d  seq %2d  reading %5d" % (time.time() - start, am_type, src, seq, reading))
print("the forwarder closed the connection")

struct.unpack(">BHHBBB", ...) reads the first eight bytes as one byte, two 2-byte numbers and three bytes, big-endian (the >), because nesC's nx_ types are sent most significant byte first on every platform. The times on the left are real seconds since the program connected, so they differ on every run and on every machine:

$ bash serve.sh 7
$ timeout 30 python3 reader.py
  1.4 s  type 10  from 1  seq  0  reading 32768
  1.4 s  type 10  from 1  seq  1  reading 34405
  2.0 s  type 10  from 1  seq  2  reading 36039
  2.0 s  type 10  from 1  seq  3  reading 37664
  3.4 s  type 10  from 1  seq  4  reading 39277
  3.4 s  type 10  from 1  seq  5  reading 40874
  4.0 s  type 10  from 1  seq  6  reading 42451
the forwarder closed the connection

The same seven readings, now as numbers: from 32768 rising, as a sine wave does from its middle. SineSensorC computes each reading as 32768 plus 32768 times the sine of the reading's count divided by 20 (tos/system/SineSensorC.nc), so the first is exactly 32768.

Watch the times. The readings leave the mote exactly half a simulated second apart, and in this run they reached the PC in pairs: two at 1.4 seconds, two at 2.0, two at 3.4, and the last at 4.0. The throttle is coarse. Its source compares simulated time with the real clock rounded down to whole seconds (tos/lib/tossim/sf/Throttle.cpp), so the simulation can run up to about a second ahead of real time before it is held back, and the packets arrive in bunches. The data are exactly right; only their arrival is uneven. On a real mote the readings would arrive every half second, because the real clock is the only clock there is.

munotes.in81

Practical 8: Mote-to-PC Serial Communication Through the SerialForwarder

Step 5: decoding the bytes

Take Listen's first line and decode it by hand, which is exactly what reader.py does. The header's layout is in TinyOS's own source:

$ sed -n '/typedef nx_struct serial_header/,/serial_header_t;/p' $TOSDIR/lib/serial/Serial.h
typedef nx_struct serial_header {
  nx_am_addr_t dest;
  nx_am_addr_t src;
  nx_uint8_t length;
  nx_am_group_t group;
  nx_am_id_t type;
} serial_header_t;
$ grep -n 'TOS_SERIAL_ACTIVE_MESSAGE_ID =' $TOSDIR/lib/serial/Serial.h
95:  TOS_SERIAL_ACTIVE_MESSAGE_ID = 0,

The forwarder hands over the packet without its serial framing (the delimiters, protocol byte, sequence byte and CRC TEP 113 describes), so a packet begins at the dispatch byte. Listen's first line, byte by byte:

BytesFieldValueMeaning
00dispatch0an active message (TOS_SERIAL_ACTIVE_MESSAGE_ID)
FF FFdest65535the broadcast address the mote sent to
00 01src1mote 1
04length4four bytes of payload follow
00group0the AM group
0Atype10AM_READING_MSG
00 00seq0the first reading
80 00reading32768the sensor's value

The last row is the one to be able to do in your head: 80 00 is hexadecimal, and 0x8000 is 32768.

The design document and the code disagree, and the code is right. TEP 113 prints the serial active-message header with a single address field, addr, and its example frame gives the protocol byte 0x40 as SERIAL_PROTO_ACK. TinyOS 2.1.2's own Serial.h, printed above, has two addresses, dest and src, and sets SERIAL_PROTO_ACK to 67. The bytes Listen printed have both addresses. When a document and the source it describes disagree, believe the source, and say which you used.

Step 6: the TinyOS way, with mig and MsgReader

Decoding by hand is how you understand a packet. It is not how you should write every monitoring tool, because the moment the message gains a field, every decoder must be changed to match. TinyOS's answer is mig, the message interface generator: it reads the message structure straight out of the application's header file and writes a class that knows every field's offset and size.

$ mig java -target=null -java-classname=ReadingMsg SerialReporter.h reading_msg -o ReadingMsg.java
$ javac ReadingMsg.java
$ grep -c 'public int get_' ReadingMsg.java
2
$ bash serve.sh 5
$ timeout 30 java net.tinyos.tools.MsgReader ReadingMsg -comm sf@localhost:9002
1790766026432: Message <ReadingMsg>
  [seq=0x0]
  [reading=0x8000]

1790766026564: Message <ReadingMsg>
  [seq=0x1]
  [reading=0x8665]

1790766027088: Message <ReadingMsg>
  [seq=0x2]
  [reading=0x8cc7]

sf@localhost:9002 died - exiting (java.io.IOException: end-of-stream)
munotes.in82

Practical 8: Mote-to-PC Serial Communication Through the SerialForwarder

mig read reading_msg out of SerialReporter.h and wrote a Java class with a getter for each of its two fields; -target=null tells it to lay the message out without any mote platform, which is right because nx_ types are laid out the same everywhere (on this Ubuntu, -target=micaz fails: the TinyOS laboratory chapter met the same AVR compiler problem). MsgReader then connected to the forwarder, recognised the type 10 messages as ReadingMsg, and printed each with its fields by name: seq=0x0, reading=0x8000 is the same packet as Listen's first line and reader.py's first line. The long number before each message is the PC's clock in milliseconds, which is why it differs on every run. A five-second run gives three readings, at 3.5, 4.0 and 4.5 simulated seconds.

Add a field to reading_msg, run mig again, and MsgReader prints it too, while reader.py would have to be changed by hand. That is the point of generating the class.

Procedure

  1. Write a module that reads a sensor every half second and sends each reading with a sequence number through SerialAMSenderC, and wire it to SerialActiveMessageC.
  2. Build it with make micaz sim-sf, which includes TOSSIM's SerialForwarder.
  3. Run the simulation with a SerialForwarder on port 9002 and a Throttle, in one terminal.
  4. In a second terminal, watch the raw packets with java net.tinyos.tools.Listen -comm sf@localhost:9002.
  5. Decode one packet by hand: dispatch byte, serial header, payload.
  6. Write a Python program that speaks the SerialForwarder protocol and prints each reading as it arrives.
  7. Generate a message class with mig and read the same data with MsgReader.

Observations

ObservedValue
Readings in a 7-second run (boot at 3 s, one every 0.5 s)7, sequence 0 to 6
Listen's line for reading 000 FF FF 00 01 04 00 0A 00 00 80 00
Header, decodeddispatch 0, dest 65535, src 1, length 4, group 0, type 10
Readings32768, 34405, 36039, 37664, 39277, 40874, 42451
Arrival at the PC, reader.pyin pairs, not evenly: the throttle's coarse pacing
miga ReadingMsg class with 2 getters
MsgReader, 5-second run3 messages, fields printed by name
TEP 113 against Serial.hthe TEP shows one address and SERIAL_PROTO_ACK 0x40; the code has dest and src and SERIAL_PROTO_ACK 67

Result

A mote application was written that reads TinyOS's SineSensorC every half second and sends each reading with a sequence number over the serial port, through SerialActiveMessageC and SerialAMSenderC. Built with make micaz sim-sf, it ran in TOSSIM with a SerialForwarder on TCP port 9002 and a Throttle holding it to the real clock. Three PC programs received the same seven readings as they were sent: TinyOS's Listen printed the raw bytes, a Python program speaking the SerialForwarder protocol decoded the header and payload and printed each reading with its arrival time, and MsgReader printed the fields by name using a class generated by mig. The bytes were decoded by hand against TinyOS 2.1.2's Serial.h, which differs from the header in TEP 113. The throttle's pacing was coarse, so readings sent half a second apart arrived in pairs.

munotes.in83

Practical 8: Mote-to-PC Serial Communication Through the SerialForwarder

Where marks are lost

Building with make micaz sim. Without -sf there is no SerialForwarder and no SerialForwarder in Python.

Wiring the radio components. Serial needs SerialActiveMessageC and SerialAMSenderC; AMSenderC sends over the radio.

Starting the PC program before the simulation. There is nothing listening on port 9002 yet, and the connection is refused. Start the simulation first, and give the mote a late boot so the PC program has time to connect.

Forgetting sf.process() in the loop. Nothing is passed on, and the PC sees nothing.

Reading the bytes little-endian. nx_ types are big-endian: 80 00 is 32768, not 128.

Quoting TEP 113's header. TinyOS 2.1.2's serial header has a destination and a source.

Believing the arrival times. In simulation they show the throttle, not the mote. The sequence numbers are the evidence that nothing was lost.

Using -target=micaz with mig on this Ubuntu. It fails; -target=null is right for nx_ messages.

For the journal

Aim; the serial stack, the SerialForwarder and packet sources, briefly; the four files, the Makefile, sf.py and serve.sh; Listen's output; reader.py and its output; the byte-by-byte decoding table; the note on TEP 113 against Serial.h; the mig and MsgReader commands and output; the observation table; the result.

Quick revision

  • Serial uses active messages too: SerialActiveMessageC, SerialAMSenderC, SerialAMReceiverC.
  • The SerialForwarder shares one mote's serial port with many PC programs over TCP.
  • make micaz sim-sf builds TOSSIM with a SerialForwarder; SerialForwarder(9002) and sf.process() run it.
  • Throttle(t, 10) holds simulated time to the real clock, coarsely.
  • -comm sf@localhost:9002, or serial@/dev/ttyUSB0:micaz (57600) or :telosb (115200) on real hardware; MOTECOM if no -comm.
  • SerialForwarder protocol: exchange "U ", then each packet is a length byte and the packet.
  • Packet: dispatch 0, then dest, src, length, group, type, then the payload; big-endian.
  • mig java -target=null generates a message class from the header; MsgReader prints fields by name.
  • TEP 113 is out of date on the header; Serial.h is the authority.

Questions you must be able to answer

1. What is the SerialForwarder, and why is it needed? A small server that owns the mote's serial port and passes each packet to every program connected to it over TCP, and their packets to the mote. Without it only one program could use the port at a time.

munotes.in84

Practical 8: Mote-to-PC Serial Communication Through the SerialForwarder

2. What does make micaz sim-sf add to make micaz sim? A SerialForwarder and a Throttle inside TOSSIM, so that the simulated mote's serial port can be reached over TCP by the same PC tools a real mote uses.

3. Decode 00 FF FF 00 01 04 00 0A 00 00 80 00. Dispatch 0, an active message; destination 0xFFFF, broadcast; source 1; length 4; group 0; type 10; then the payload, sequence 0 and reading 0x8000, which is 32768.

4. Why is 80 00 32768 and not 128? Because nesC's network types are big-endian: the first byte is the most significant, 0x80 times 256.

5. What is the SerialForwarder handshake? Each side sends the two characters U and space; after that every packet is sent as one length byte followed by the packet.

6. Why did the readings arrive in pairs? TOSSIM's Throttle compares simulated time with real time rounded down to whole seconds, so the simulation can run ahead by up to about a second before it is held back, and packets are released in bunches.

7. What does mig do, and why use it? It generates a message class from the application's own header, with the offset and size of every field, so PC tools can read the fields by name and do not have to be rewritten when the message changes.

8. How would you read the same data from a real MICAz on a USB port? Start a SerialForwarder on the port, or point the tool at it directly: java net.tinyos.tools.Listen -comm serial@/dev/ttyUSB0:micaz, which the TinyOS tutorial gives as 57600 baud for a MICAz.

Contents This chapter on its own page

munotes.in85

Chapter Eleven

Practical 9: A Simple Ad Hoc Network in TOSSIM and Its Broadcast

Syllabus topic Module 1, "Creation of Simple Adhoc Network using TOSSIM: Create a multi-node ad-hoc network in TOSSIM and evaluate broadcast communication among nodes."

Aim

To create a multi-node ad hoc network in TOSSIM, and to evaluate broadcast communication among its nodes.

What you need to know before you start

An ad hoc network is a network with no infrastructure: no access point, no base station that everything talks through, no cables. The nodes are the network. Each one talks directly to the nodes within its radio range, its neighbours, and a message for a node further away has to be passed on by the nodes in between, hop by hop. Every node is both a source of data and a relay for others. A sensor network is an ad hoc network of this kind.

Unicast and broadcast. A unicast packet is addressed to one node. A broadcast packet is addressed to everyone who can hear it: in TinyOS, to AM_BROADCAST_ADDR, address 0xFFFF. One radio transmission reaches all the sender's neighbours at once, which is the great advantage of radio. But it reaches only the neighbours.

Flooding is how a broadcast reaches the whole network: every node that receives the message transmits it once more, so it spreads outwards ring by ring from its origin. Three things make flooding work, and this practical measures each:

  1. Duplicate suppression. A node hears the same message from several neighbours. It must pass on only the first copy, recognised by a sequence number, or every copy multiplies.
  2. A hop limit (TTL). Each copy carries how many hops it has travelled, and is not passed on past a limit, so a mistake cannot circulate for ever.
  3. Jitter. Neighbours that hear the same copy at the same instant would all retransmit at the same instant and collide. Each waits a short random time first.

Without the first, the result is the broadcast storm: redundant retransmissions, contention for the channel and collisions, all at once.

Step 1: a network made of distances

In TOSSIM a network is nothing but the gain of every link (Practical 7). For a network of more than a few motes those gains must come from somewhere, and TinyOS's answer is a radio propagation model, documented in its topology guide: the signal loses a fixed amount at a reference distance d0, then more with every further distance, at a rate set by the path loss exponent n, plus a random variation for obstacles and reflections, called shadowing, of standard deviation sigma:

Path loss at distance d = PL(d0) + 10 × n × log10(d / d0) + shadowing

The guide's sample values are PL(d0) 55 dB at d0 of 1 metre, n of 3.0 and sigma of 4 dB, and it gives measured values for real places: a football field (55.4, 4.7, 3.2) and the aisle of a building (52.1, 3.3, 5.5). This practical uses the sample values, with sixteen motes on a 4 by 4 grid, 10 metres apart.

munotes.in86

Practical 9: A Simple Ad Hoc Network in TOSSIM and Its Broadcast

Step 2: TinyOS's own topology tool, and why it is not used here

TinyOS ships the model as a Java program, LinkLayerModel, driven by a configuration file:

$ mkdir ~/Flood
$ cd ~/Flood
$ printf 'PATH_LOSS_EXPONENT = 3.0;\nSHADOWING_STANDARD_DEVIATION = 4.0;\nD0 = 1.0;\nPL_D0 = 55.0;\nNOISE_FLOOR = -105.0;\nS11 = 0;\nS12 = 0;\nS21 = 0;\nS22 = 0;\nWHITE_GAUSSIAN_NOISE = 4;\nTOPOLOGY = 1;\nGRID_UNIT = 10.0;\nNUMBER_OF_NODES = 16;\n' > grid.cfg
$ java net.tinyos.sim.LinkLayerModel grid.cfg
Topology ...			done
Radio Pt and Pn ...		done
Links Gain .....		done
Printing Output File ...	done
$ grep -c gain linkgain.out
240
$ head -4 linkgain.out
gain	0	1	-92.50
gain	1	0	-92.50
gain	0	2	-103.41
gain	2	0	-103.41

It placed the sixteen motes (topology 1 is a grid, with GRID_UNIT metres between them), computed a gain for each of the 240 directed links, and wrote them to linkgain.out in the format TOSSIM scripts read: gain, source, destination, gain. The four S values set to zero ask for symmetric links, the same gain in both directions.

But run it twice and the numbers change. Its source makes its random numbers with new Random(), which seeds from the clock, so every run is a different network. A journal whose results depend on a network nobody can recreate cannot be checked, so the experiment below uses a short Python program that applies the same formula (line 566 of LinkLayerModel.java) with a fixed seed. topo.py:

# topo.py: a grid of motes and the gain of every link, by the log-normal
# path-loss formula TinyOS's LinkLayerModel uses, with a fixed seed.
import math
import random

random.seed(1)
SIDE, SPACING = 4, 10.0                       # a 4 x 4 grid, 10 m apart
PL_D0, EXPONENT, SIGMA, D0 = 55.0, 3.0, 4.0, 1.0

nodes = [(n, (n % SIDE) * SPACING, (n // SIDE) * SPACING) for n in range(SIDE * SIDE)]
for a, xa, ya in nodes:
    for b, xb, yb in nodes:
        if a < b:
            d = math.hypot(xa - xb, ya - yb)
            gain = -PL_D0 - 10 * EXPONENT * math.log10(d / D0) + random.gauss(0, SIGMA)
            print("gain %d %d %.2f" % (a, b, gain))
            print("gain %d %d %.2f" % (b, a, gain))
$ python3 topo.py > topo.txt
$ wc -l < topo.txt
240
$ head -2 topo.txt
gain 0 1 -79.85
gain 1 0 -79.85

A link is usable when its gain leaves a clear margin above the noise. Practical 7 found that a quiet room's links were perfect down to minus 91 dB; taking minus 90 dB as the edge of a good link, each mote's neighbours are:

munotes.in87

Practical 9: A Simple Ad Hoc Network in TOSSIM and Its Broadcast

$ awk '$4 > -90 {nb[$2] = nb[$2] " " $3} END {for (n = 0; n < 16; n++) print "node " n ":" nb[n]}' topo.txt
node 0: 1 2 4
node 1: 0 2 4 5
node 2: 0 1 3 5 6 7
node 3: 2
node 4: 0 1 8 9 12
node 5: 1 2 6 8 9 13
node 6: 2 5 7 9 11
node 7: 2 6 11
node 8: 4 5 9 12 13
node 9: 4 5 6 8 10 11 12 13
node 10: 9 11 13
node 11: 6 7 9 10 14 15
node 12: 4 8 9 13 14
node 13: 5 8 9 10 12
node 14: 11 12
node 15: 11

Every mote has at least one good neighbour, and the network is connected, but not all of it within one hop: node 0's neighbours are only 1, 2 and 4, and node 3 and node 15 each have a single good link. A message from node 0 to node 15 must be relayed several times. That is what makes this an ad hoc network in the sense that matters: the only way across it is through the other nodes.

Step 3: the flooding application

Make one module do both jobs: node 0 is the origin, which starts a flood every two seconds, five in all; every other node relays. The constants, Flood.h:

#ifndef FLOOD_H
#define FLOOD_H
enum {
  AM_FLOOD = 8,
  ORIGIN = 0,          /* the node that starts every flood */
  FLOODS = 5,          /* how many floods it starts */
  PERIOD = 2048,       /* one every two seconds */
  JITTER = 64,         /* a relay waits 0 to 63 ms before rebroadcasting */
  SUPPRESS = 1,        /* 1: rebroadcast only the first copy; 0: every copy */
  TTL = 4              /* the most hops a copy may travel */
};
typedef nx_struct flood_msg {
  nx_uint16_t seq;     /* which flood */
  nx_uint8_t hops;     /* how many hops this copy has travelled */
} flood_msg_t;
#endif

SUPPRESS, JITTER and TTL are the three mechanisms from the introduction, and steps 5 and 6 switch two of them off. The module, FloodC.nc:

#include "Flood.h"

module FloodC {
  uses interface Boot;
  uses interface Timer<TMilli> as Start;
  uses interface Timer<TMilli> as Relay;
  uses interface Random;
  uses interface SplitControl as RadioControl;
  uses interface AMSend;
  uses interface Receive;
  uses interface AMPacket;
}
implementation {
  message_t packet;
  bool busy = FALSE;
  uint16_t lastSeq = 0;        /* the newest flood this node has seen */
  uint16_t nextSeq = 1;        /* the origin's next flood */
  uint8_t pendingHops;

  void transmit(uint16_t seq, uint8_t hops) {
    flood_msg_t* m = (flood_msg_t*) call AMSend.getPayload(&packet, sizeof(flood_msg_t));
    if (busy) return;
    m->seq = seq;
    m->hops = hops;
    if (call AMSend.send(AM_BROADCAST_ADDR, &packet, sizeof(flood_msg_t)) == SUCCESS) {
      busy = TRUE;
      dbg("Flood", "send flood %u hops %u at %s\n", seq, hops, sim_time_string());
    }
  }

  event void Boot.booted() { call RadioControl.start(); }

  event void RadioControl.startDone(error_t err) {
    if (TOS_NODE_ID == ORIGIN)
      call Start.startPeriodic(PERIOD);
  }

  event void RadioControl.stopDone(error_t err) {}

  event void Start.fired() {
    if (nextSeq > FLOODS) { call Start.stop(); return; }
    lastSeq = nextSeq;
    transmit(nextSeq++, 0);
  }

  event void Relay.fired() { transmit(lastSeq, pendingHops); }

  event void AMSend.sendDone(message_t* msg, error_t err) { busy = FALSE; }

  event message_t* Receive.receive(message_t* msg, void* payload, uint8_t len) {
    flood_msg_t* m = (flood_msg_t*) payload;
    uint8_t hops = m->hops + 1;
    if (m->seq > lastSeq) {
      lastSeq = m->seq;
      dbg("Flood", "first flood %u from %u hops %u at %s\n", m->seq, call AMPacket.source(msg), hops, sim_time_string());
      if (TOS_NODE_ID != ORIGIN && hops < TTL) {
        pendingHops = hops;
        call Relay.startOneShot(JITTER ? call Random.rand16() % JITTER : 0);
      }
    } else {
      dbg("Flood", "duplicate flood %u from %u hops %u\n", m->seq, call AMPacket.source(msg), hops);
      if (!SUPPRESS && TOS_NODE_ID != ORIGIN && hops < TTL && !(call Relay.isRunning())) {
        pendingHops = hops;
        call Relay.startOneShot(JITTER ? call Random.rand16() % JITTER : 0);
      }
    }
    return msg;
  }
}
munotes.in88

Practical 9: A Simple Ad Hoc Network in TOSSIM and Its Broadcast

The origin starts a periodic timer and on each firing broadcasts the next flood, with 0 hops.

A relay, on receiving a flood, compares its sequence number with the newest it has seen, lastSeq. A higher number is a first copy: it is logged with the neighbour it came from, its hop count and the time, and if the hop count is below TTL the relay starts a one-shot timer of up to 63 binary milliseconds and, when it fires, broadcasts the flood with its own hop count. Anything else is a duplicate and is only logged, unless SUPPRESS is 0, when it is relayed too (though never while a relay is already waiting, Relay.isRunning()).

AMPacket.source(msg) gives the address of the node that sent the packet, the neighbour it came from, which is how the log can say who passed the flood on. FloodAppC.nc wires it all:

#include "Flood.h"

configuration FloodAppC {}
implementation {
  components MainC, FloodC as App, RandomC, ActiveMessageC;
  components new TimerMilliC() as StartTimer, new TimerMilliC() as RelayTimer;
  components new AMSenderC(AM_FLOOD), new AMReceiverC(AM_FLOOD);

  App.Boot -> MainC;
  App.Start -> StartTimer;
  App.Relay -> RelayTimer;
  App.Random -> RandomC;
  App.RadioControl -> ActiveMessageC;
  App.AMSend -> AMSenderC;
  App.Receive -> AMReceiverC;
  App.AMPacket -> AMSenderC;
}
COMPONENT=FloodAppC
override GCC = gcc-10
export GCC
PFLAGS += -fgnu89-inline -fsigned-char
include $(MAKERULES)

The script loads the gains from the topology file, gives all sixteen motes the quiet noise trace, and runs twelve simulated seconds, enough for five floods. run.py:

munotes.in89

Practical 9: A Simple Ad Hoc Network in TOSSIM and Its Broadcast

from TOSSIM import *
import sys

t = Tossim([])
t.randomSeed(1)
r = t.radio()
for line in open(sys.argv[1]):
    words = line.split()
    if words and words[0] == "gain":
        r.add(int(words[1]), int(words[2]), float(words[3]))
noise = open(sys.argv[2]).readlines()[:100]
for n in range(16):
    m = t.getNode(n)
    for reading in noise:
        m.addNoiseTraceReading(int(reading))
    m.createNoiseModel()
    m.bootAtTime(1000 * n + 1)
t.addChannel("Flood", sys.stdout)
while t.time() < 12 * t.ticksPerSecond():
    t.runNextEvent()

Step 4: the flood, measured

$ make micaz sim 2>&1 | tail -1
*** Successfully built micaz TOSSIM library.
$ python2 run.py topo.txt $TOSDIR/lib/tossim/noise/casino-lab.txt > flood.log
$ grep -c . flood.log
521
$ grep 'flood 1 ' flood.log | head -8
DEBUG (0): send flood 1 hops 0 at 0:0:2.000000010
DEBUG (2): first flood 1 from 0 hops 1 at 0:0:2.002044675
DEBUG (1): first flood 1 from 0 hops 1 at 0:0:2.002044675
DEBUG (4): first flood 1 from 0 hops 1 at 0:0:2.002044675
DEBUG (4): send flood 1 hops 1 at 0:0:2.004883222
DEBUG (0): duplicate flood 1 from 4 hops 2
DEBUG (1): duplicate flood 1 from 4 hops 2
DEBUG (5): first flood 1 from 4 hops 2 at 0:0:2.013504388

Flood 1 in its first moments. Node 0 broadcast at 2.000 seconds. Its three neighbours, 1, 2 and 4, heard it at the same instant, 2.002 seconds, which is the time one short frame takes on the air. Node 4's random delay ran out first and it rebroadcast, and the copy went back to node 0 and node 1 as duplicates: a broadcast cannot be aimed, so every relay's transmission also reaches nodes that already have the message. Node 5 heard it for the first time, two hops from the origin. 521 log lines in all, for five floods.

summary.awk turns the log into the measurements that evaluate a broadcast: how many nodes each flood reached, how many transmissions it cost, how many duplicate copies were heard, how many hops it spread and how long the last node waited:

# summary.awk: what each flood achieved, read from TOSSIM's log.
function secs(t, p) { split(t, p, ":"); return p[1] * 3600 + p[2] * 60 + p[3] }
$3 == "send"      { sends[$5]++; if ($7 == 0) start[$5] = secs($9) }
$3 == "first"     { reached[$5]++
                    if ($9 > deepest[$5]) deepest[$5] = $9
                    if (secs($11) > last[$5]) last[$5] = secs($11) }
$3 == "duplicate" { dups[$5]++ }
END {
    for (f = 1; f <= 5; f++)
        printf "flood %d: reached %2d of 15, sent %2d times, %3d duplicates, %d hops deep, last after %3.0f ms\n",
               f, reached[f], sends[f], dups[f], deepest[f], (last[f] - start[f]) * 1000
}
$ awk -f summary.awk flood.log
flood 1: reached 15 of 15, sent 14 times,  79 duplicates, 4 hops deep, last after  43 ms
flood 2: reached 15 of 15, sent 15 times,  67 duplicates, 4 hops deep, last after  65 ms
flood 3: reached 15 of 15, sent 15 times,  68 duplicates, 4 hops deep, last after  24 ms
flood 4: reached 15 of 15, sent 15 times,  81 duplicates, 4 hops deep, last after  78 ms
flood 5: reached 15 of 15, sent 16 times,  76 duplicates, 3 hops deep, last after  43 ms
$ awk '$3 == "first" && $5 == 1 {gsub(/[():]/, "", $2); print "node " $2 ": " $9 " hops, from node " $7}' flood.log | sort -k2 -n
node 1: 1 hops, from node 0
node 2: 1 hops, from node 0
node 3: 4 hops, from node 6
node 4: 1 hops, from node 0
node 5: 2 hops, from node 4
node 6: 3 hops, from node 9
node 7: 2 hops, from node 1
node 8: 2 hops, from node 4
node 9: 2 hops, from node 4
node 10: 3 hops, from node 9
node 11: 3 hops, from node 9
node 12: 2 hops, from node 4
node 13: 3 hops, from node 9
node 14: 3 hops, from node 9
node 15: 4 hops, from node 14
munotes.in90

Practical 9: A Simple Ad Hoc Network in TOSSIM and Its Broadcast

The 4 by 4 grid of Practical 9. Faint lines are links better than minus 90 dB; arrows show where each node's first copy of flood 1 came from, with the hop count under each node.

Figure 11.1 Flood 1: the path each node's first copy took

Every flood reached all fifteen other nodes, within 24 to 78 milliseconds. It cost 14 to 16 transmissions each, about one per node, because with duplicate suppression each node passes on only its first copy; the variation is the hop limit, since a node that is already four hops out does not relay.

It also heard 67 to 81 duplicates per flood, about five per node. That is the price of flooding: every node receives the message once from each neighbour that relays it, and only the first copy is any use.

The first copy did not always take the shortest way. Node 6 is a good neighbour of node 2, one hop from the origin, yet its first copy arrived at three hops through node 9. Node 3's only good link is to node 2, yet its first copy came at four hops from node 6, over a link weaker than minus 90 dB that happened to succeed. Node 2 did relay flood 1 (fourteen transmissions include it), so its copy was lost at both nodes, most likely to a collision with another relay's transmission, and the flood arrived another way. Redundancy is what made flooding reliable here: a copy lost on one path was replaced by one on another.

munotes.in91

Practical 9: A Simple Ad Hoc Network in TOSSIM and Its Broadcast

Step 5: the broadcast storm

Now switch duplicate suppression off, so that a relay passes on every copy it hears, not just the first (still within the hop limit, and never while a relay is already waiting), and run the same five floods over the same network:

$ sed -i 's/SUPPRESS = 1/SUPPRESS = 0/' Flood.h
$ make micaz sim 2>&1 | tail -1
*** Successfully built micaz TOSSIM library.
$ python2 run.py topo.txt $TOSDIR/lib/tossim/noise/casino-lab.txt > storm.log
$ awk -f summary.awk storm.log
flood 1: reached 15 of 15, sent 41 times, 199 duplicates, 4 hops deep, last after  43 ms
flood 2: reached 15 of 15, sent 30 times, 145 duplicates, 4 hops deep, last after  69 ms
flood 3: reached 15 of 15, sent 34 times, 157 duplicates, 3 hops deep, last after  93 ms
flood 4: reached 15 of 15, sent 40 times, 212 duplicates, 3 hops deep, last after  36 ms
flood 5: reached 15 of 15, sent 26 times, 130 duplicates, 4 hops deep, last after  44 ms

The same network and the same five floods, and two to three times the transmissions: 26 to 41 per flood instead of 14 to 16, and 130 to 212 duplicates instead of 67 to 81. Every flood still reached all fifteen nodes, so all of that extra transmitting bought nothing: every node already had the message.

This is the broadcast storm, and here it is small only because two things hold it down. The hop limit stops any copy after four hops, and a node does not queue a second relay while one is waiting. Remove the hop limit as well and a flood would never stop, since every copy would breed copies. In a sensor network, where Practical 1 showed a transmission costs as much as thousands of instructions, the storm is a waste of energy even when it does no other harm.

Step 6: no jitter

Put suppression back and take the jitter away instead, so that every relay transmits the moment its timer is started:

$ sed -i 's/SUPPRESS = 0/SUPPRESS = 1/; s/JITTER = 64/JITTER = 0/' Flood.h
$ grep -E 'SUPPRESS =|JITTER =' Flood.h
  JITTER = 0,         /* a relay waits 0 to 63 ms before rebroadcasting */
  SUPPRESS = 1,        /* 1: rebroadcast only the first copy; 0: every copy */
$ make micaz sim 2>&1 | tail -1
*** Successfully built micaz TOSSIM library.
$ python2 run.py topo.txt $TOSDIR/lib/tossim/noise/casino-lab.txt > nojitter.log
$ awk -f summary.awk nojitter.log
flood 1: reached 15 of 15, sent 13 times,  34 duplicates, 4 hops deep, last after  17 ms
flood 2: reached 14 of 15, sent 13 times,  35 duplicates, 4 hops deep, last after  15 ms
flood 3: reached 15 of 15, sent 14 times,  32 duplicates, 4 hops deep, last after  10 ms
flood 4: reached 14 of 15, sent 13 times,  23 duplicates, 4 hops deep, last after  25 ms
flood 5: reached 15 of 15, sent 12 times,  31 duplicates, 4 hops deep, last after  29 ms
munotes.in92

Practical 9: A Simple Ad Hoc Network in TOSSIM and Its Broadcast

Without jitter the floods were faster, 10 to 29 milliseconds, because no relay waited. But two of the five floods missed a node, and the duplicates fell to 23 to 35. Fewer duplicates here is not an improvement. Neighbours that heard a copy at the same instant now relayed at the same instant, their transmissions overlapped at the nodes around them, and the colliding copies were destroyed rather than delivered twice. When the colliding copies happened to be the only ones that could reach a node, that node never heard the flood at all.

A few tens of milliseconds of random waiting is what separates a flood that reaches everyone from one that usually does.

Procedure

  1. Generate a 16-mote grid topology with the log-normal path-loss model, using a fixed seed.
  2. List each mote's neighbours: the motes it reaches at better than minus 90 dB.
  3. Write a flooding application: node 0 starts five floods; every other node relays the first copy of each after a random delay of up to 63 ms, up to a hop limit of 4.
  4. Run it in TOSSIM with the quiet noise trace and summarise each flood: nodes reached, transmissions, duplicates, depth and time.
  5. Turn duplicate suppression off, rebuild and compare.
  6. Turn the jitter off instead, rebuild and compare.

Observations

Per flood, five floodsSuppression and jitterNo suppression (storm)No jitter
Nodes reached, of 1515 every time15 every time15, 14, 15, 14, 15
Transmissions14 to 1626 to 4112 to 14
Duplicates heard67 to 81130 to 21223 to 35
Hops to the furthest node3 or 43 or 44
Last node reached after24 to 78 ms36 to 93 ms10 to 29 ms
The networkValue
Motes16, a 4 by 4 grid, 10 m apart
Path lossPL(d0) 55 dB at 1 m, exponent 3.0, shadowing 4 dB, fixed seed
Directed links240, of which those better than -90 dB give each node 1 to 8 neighbours
Noisethe quiet casino-lab trace

Result

A 16-node ad hoc network was built in TOSSIM from a log-normal path-loss model with a fixed seed, and a flooding broadcast was implemented in which node 0 starts five floods and every other node relays the first copy of each after a random delay of up to 63 milliseconds, within a limit of four hops. Every flood reached all fifteen other nodes within 78 milliseconds, at a cost of about one transmission per node and about five duplicate receptions per node, and several nodes received their first copy over a longer path than the shortest because a copy on the short path was lost. Without duplicate suppression the same floods took two to three times as many transmissions and duplicates and reached no one more: a broadcast storm, bounded only by the hop limit. Without jitter the floods were faster but two of five missed a node, because relays transmitting at the same instant collided.

munotes.in93

Practical 9: A Simple Ad Hoc Network in TOSSIM and Its Broadcast

Where marks are lost

Relaying every copy. Without a sequence number and a record of the newest one seen, every node repeats every copy and the network floods itself.

No hop limit. One bug, and copies circulate for ever.

No jitter. Neighbours that hear the same copy together transmit together and collide.

Reading fewer duplicates as better. Without jitter they fell because copies were destroyed in collisions, and two floods missed a node.

Expecting the flood to follow the shortest path. Each node takes whichever copy arrives first, and a lost copy sends it the long way.

Using LinkLayerModel's output in a journal without saying it is random. Its network changes on every run; save the file you used, or seed a generator of your own.

Counting a node that sent the flood as having received it. The origin hears its neighbours' relays as duplicates; it is not one of the fifteen reached.

For the journal

Aim; ad hoc networks, broadcast and flooding, with the three mechanisms; the path-loss formula and the parameters used; the LinkLayerModel run and why it was not used for the results; topo.py and the neighbour table; Flood.h, FloodC.nc, FloodAppC.nc, the Makefile and run.py; the first lines of the log explained; summary.awk and the three summaries; the figure of the first copies, or the hop table; the observation tables; the result.

Quick revision

  • Ad hoc: no infrastructure; every node talks to its neighbours and relays for others.
  • Broadcast is to AM_BROADCAST_ADDR, 0xFFFF, and reaches only one hop.
  • Flooding: every node rebroadcasts, so the message spreads ring by ring.
  • Duplicate suppression (a sequence number), a hop limit (TTL) and jitter make it work.
  • Path loss = PL(d0) + 10 n log10(d/d0) + shadowing; TinyOS's LinkLayerModel generates gains from it but is not seeded.
  • Measured: every flood reached 15 of 15 in under 80 ms, with about 15 transmissions and about 75 duplicates.
  • No suppression: 26 to 41 transmissions for the same result, the broadcast storm.
  • No jitter: faster, fewer duplicates because of collisions, and two floods missed a node.
  • The first copy does not always take the shortest path.
munotes.in94

Practical 9: A Simple Ad Hoc Network in TOSSIM and Its Broadcast

Questions you must be able to answer

1. What makes a network ad hoc? It has no infrastructure. Nodes talk directly to the nodes in range and relay messages for each other, so every node is both an end point and a router.

2. How does flooding deliver a broadcast to nodes out of range of the origin? Every node that receives it transmits it once more, so it spreads outwards hop by hop until every connected node has heard it.

3. Why does each node pass on only the first copy? Because it hears the same message from several neighbours, and relaying every copy multiplies the transmissions without delivering anything new: here, 26 to 41 transmissions per flood instead of 14 to 16.

4. What is the hop limit for? It stops any copy after a set number of hops, so that a mistake, such as duplicate suppression failing, cannot keep a message circulating for ever.

5. Why did the relays wait a random time before rebroadcasting? Neighbours that hear a copy at the same instant would otherwise transmit at the same instant and collide. Without the delay, two of five floods missed a node.

6. Without jitter there were fewer duplicates. Why is that not good news? Because they were missing through collisions, not avoided: copies that overlapped were destroyed, and when they were the only copies that could reach a node, that node never heard the flood.

7. Node 6 is one hop from node 2, which is one hop from the origin. Why did its first copy arrive at three hops? Node 2's relay did not reach it, most likely lost to a collision with another relay, and the next copy to arrive came through node 9. Each node takes whichever copy arrives first.

8. Why was TinyOS's LinkLayerModel not used for the measured network? It seeds its random numbers from the clock, so every run makes a different network and the results could not be repeated or checked. The same formula with a fixed seed gives the same network every time.

9. What is the broadcast storm? The flood of redundant retransmissions, channel contention and collisions that results when nodes rebroadcast without restraint. Duplicate suppression, the hop limit and jitter are its remedies.

Contents This chapter on its own page

munotes.in95

Chapter Twelve

Practical 10: Routing Table Generation and Analysis

Syllabus topic Module 1, "Routing Table Generation and Analysis in WSN: Implement a simple routing mechanism and analyze routing table updates during topology changes."

Aim

To implement a simple routing mechanism for a wireless sensor network, to generate the routing tables it builds, and to analyse how the tables are updated when the topology changes.

What you need to know before you start

Routing is choosing the path a packet takes through a multi-hop network. Practical 9's flood reached everyone by sending everywhere; routing sends a packet to one destination along one path, which costs far fewer transmissions. Each node keeps a routing table with one row per destination:

FieldMeaning
destinationthe node the row is about
next hopthe neighbour to hand a packet for that destination to
costhow far away the destination is; here, the number of hops

Distance vector is the simplest way to build those tables without any node knowing the whole network. Each node regularly broadcasts its whole table, its vector of distances, to its neighbours. A node that hears a neighbour say "I can reach D at cost c" knows it can reach D at cost c + 1 through that neighbour, and keeps whichever neighbour offers the lowest cost. Starting from knowing only itself, every node learns every destination one hop further out with each round of advertisements. This is the Bellman-Ford idea, and RIP, one of the oldest Internet routing protocols, works this way.

Three rules make it work in practice, and this chapter's program has all three:

  1. Believe your next hop. If the neighbour you already route through reports a different cost, accept it, even if it is worse; it is your only source of truth about that route.
  2. Forget what is not refreshed. A route not confirmed for a while (here 3.5 seconds, about three advertisements) is marked unreachable, because the neighbour may have gone.
  3. Stop at infinity. One cost stands for "unreachable". Here it is 16, the value RIP uses.

Topology change is what makes routing hard. A link can fail, a node can die, and every table that depended on it must change. How the tables change, and how long they take, is the analysis MU asks for. Distance vector has one well-known failure here, counting to infinity, and this practical produces it.

Step 1: the network

Six motes with seven strong links, a network with more than one path between most pairs:

The six-node network of Practical 10. The link between 1 and 2 is cut at 15 seconds and node 4 is switched off at 30 seconds.

Figure 12.1 Six nodes, seven links, two changes

Make a folder:

$ mkdir ~/DistanceVector
$ cd ~/DistanceVector

Step 2: the routing program

DV.h, the constants and the advertisement. A node advertises, for every destination, its cost and the neighbour it goes through:

#ifndef DV_H
#define DV_H
enum {
  AM_DV = 9,
  NODES = 6,            /* nodes 0 to 5 */
  UNREACHABLE = 16,        /* the cost that means "no route" */
  NOBODY = 255,         /* the next hop of a route that has none */
  BEACON = 1024,        /* each node advertises its table about once a second */
  JITTER = 256,         /* give or take a random eighth of a second */
  TIMEOUT = 3584,       /* a route not confirmed for 3.5 seconds is dead */
  AGE = 256,            /* how often each node checks its routes' age */
  REPORT = 5120,        /* print the whole table every 5 seconds */
  SPLIT_HORIZON = 0     /* 1: ignore a neighbour's route that goes through me */
};
typedef nx_struct dv_msg {
  nx_uint8_t cost[NODES];   /* the sender's cost to every node */
  nx_uint8_t via[NODES];    /* and the neighbour it goes through */
} dv_msg_t;
#endif
munotes.in96

Practical 10: Routing Table Generation and Analysis

DistanceVectorC.nc:

#include "DV.h"

module DistanceVectorC {
  uses interface Boot;
  uses interface Timer<TMilli> as Beacon;
  uses interface Timer<TMilli> as Report;
  uses interface Timer<TMilli> as Age;
  uses interface Random;
  uses interface SplitControl as RadioControl;
  uses interface AMSend;
  uses interface Receive;
  uses interface AMPacket;
}
implementation {
  uint8_t cost[NODES];      /* my cost to each node */
  uint8_t next[NODES];      /* the neighbour I send through */
  uint32_t heard[NODES];    /* when that route was last confirmed */
  message_t packet;
  bool busy = FALSE;

  void changed(uint8_t d, char* why) {
    dbg("Route", "%s node %u: to %u via %u cost %u, %s\n", sim_time_string(),
        TOS_NODE_ID, d, next[d], cost[d], why);
  }

  event void Boot.booted() {
    uint8_t d;
    for (d = 0; d < NODES; d++) {
      cost[d] = UNREACHABLE;
      next[d] = NOBODY;
      heard[d] = 0;
    }
    cost[TOS_NODE_ID] = 0;
    next[TOS_NODE_ID] = TOS_NODE_ID;
    call RadioControl.start();
  }

  event void RadioControl.startDone(error_t err) {
    call Beacon.startOneShot(call Random.rand16() % BEACON);
    call Report.startPeriodic(REPORT);
    call Age.startPeriodic(AGE);
  }

  event void RadioControl.stopDone(error_t err) {}

  event void Age.fired() {                             /* retire unconfirmed routes */
    uint8_t d;
    uint32_t now = call Age.getNow();
    for (d = 0; d < NODES; d++)
      if (d != TOS_NODE_ID && cost[d] < UNREACHABLE && now - heard[d] > TIMEOUT) {
        cost[d] = UNREACHABLE;
        changed(d, "timed out");
      }
  }

  event void Beacon.fired() {                          /* advertise the table */
    uint8_t d;
    dv_msg_t* m = (dv_msg_t*) call AMSend.getPayload(&packet, sizeof(dv_msg_t));
    if (!busy) {
      for (d = 0; d < NODES; d++) {
        m->cost[d] = cost[d];
        m->via[d] = next[d];
      }
      if (call AMSend.send(AM_BROADCAST_ADDR, &packet, sizeof(dv_msg_t)) == SUCCESS)
        busy = TRUE;
    }
    call Beacon.startOneShot(BEACON - JITTER / 2 + call Random.rand16() % JITTER);
  }

  event void AMSend.sendDone(message_t* msg, error_t err) {
    busy = FALSE;
  }

  event message_t* Receive.receive(message_t* msg, void* payload, uint8_t len) {
    dv_msg_t* m = (dv_msg_t*) payload;
    uint8_t from = call AMPacket.source(msg);
    uint32_t now = call Beacon.getNow();
    uint8_t d, c;
    for (d = 0; d < NODES; d++) {
      if (d == TOS_NODE_ID)
        continue;
      c = m->cost[d];
      if (SPLIT_HORIZON && m->via[d] == TOS_NODE_ID)
        c = UNREACHABLE;                                  /* it goes through me: useless to me */
      if (c < UNREACHABLE)
        c++;                                           /* one more hop, through the sender */
      if (next[d] == from) {                           /* news from the neighbour I use */
        if (c < UNREACHABLE)
          heard[d] = now;
        if (c != cost[d]) {
          cost[d] = c;
          changed(d, c == UNREACHABLE ? "unreachable" : "cost changed");
        }
      } else if (c < cost[d]) {                        /* a better route */
        cost[d] = c;
        next[d] = from;
        heard[d] = now;
        changed(d, "better route");
      }
    }
    return msg;
  }

  event void Report.fired() {
    uint8_t d;
    dbg("Table", "%s node %u:", sim_time_string(), TOS_NODE_ID);
    for (d = 0; d < NODES; d++)
      if (cost[d] < UNREACHABLE)
        dbg_clear("Table", "  %u:%u/%u", d, next[d], cost[d]);
      else
        dbg_clear("Table", "  %u:-/-", d);
    dbg_clear("Table", "\n");
  }
}
munotes.in97

Practical 10: Routing Table Generation and Analysis

Read it as four jobs, one per event.

Boot. Every destination starts unreachable, except the node itself at cost 0.

Beacon: advertise. About once a second, with a random eighth of a second either way (Practical 9's jitter, for the same reason), the node broadcasts its cost and next hop to every destination.

Receive: learn. For every destination in a neighbour's advertisement, the cost through that neighbour is its cost plus one. If that neighbour is already the next hop, the new cost is believed whatever it is (rule 1), and a confirmed route has its time refreshed. Otherwise the neighbour is adopted only if it is strictly better. Every change is logged on the Route channel with the reason.

Age: forget. Four times a second the node retires any route not confirmed for 3.5 seconds (rule 2), and logs it as timed out. Aging runs on its own timer, apart from advertising, as it does in real distance-vector protocols.

Report prints the whole table every 5 seconds on the Table channel, one line per node: each entry is destination:next hop/cost, and -/- means unreachable.

SPLIT_HORIZON is off for now. Step 6 explains it.

DistanceVectorAppC.nc:

#include "DV.h"

configuration DistanceVectorAppC {}
implementation {
  components MainC, DistanceVectorC as App, RandomC, ActiveMessageC;
  components new TimerMilliC() as BeaconTimer, new TimerMilliC() as ReportTimer;
  components new TimerMilliC() as AgeTimer;
  components new AMSenderC(AM_DV), new AMReceiverC(AM_DV);

  App.Boot -> MainC;
  App.Beacon -> BeaconTimer;
  App.Report -> ReportTimer;
  App.Age -> AgeTimer;
  App.Random -> RandomC;
  App.RadioControl -> ActiveMessageC;
  App.AMSend -> AMSenderC;
  App.Receive -> AMReceiverC;
  App.AMPacket -> AMSenderC;
}
COMPONENT=DistanceVectorAppC
override GCC = gcc-10
export GCC
PFLAGS += -fgnu89-inline -fsigned-char
include $(MAKERULES)

Step 3: the script, with two topology changes

run.py builds the network from its list of links, runs until 15 seconds, cuts the link between nodes 1 and 2 in both directions, runs to 30 seconds, switches node 4 off, and runs to 50 seconds. It prints a marker line at each change, so the log reads as a story:

munotes.in98

Practical 10: Routing Table Generation and Analysis

from TOSSIM import *
import sys

LINKS = [(0, 1), (1, 2), (0, 3), (3, 4), (4, 5), (2, 5), (1, 4)]

t = Tossim([])
t.randomSeed(1)
r = t.radio()
for a, b in LINKS:
    r.add(a, b, -60.0)
    r.add(b, a, -60.0)
noise = open(sys.argv[1]).readlines()[:100]
for n in range(6):
    m = t.getNode(n)
    for reading in noise:
        m.addNoiseTraceReading(int(reading))
    m.createNoiseModel()
    m.bootAtTime(1000 * n + 1)
t.addChannel("Route", sys.stdout)
t.addChannel("Table", sys.stdout)

def run_until(seconds):
    while t.time() < seconds * t.ticksPerSecond():
        t.runNextEvent()

run_until(15)
print "---- 15 s: the link between 1 and 2 is cut"
r.remove(1, 2)
r.remove(2, 1)
run_until(30)
print "---- 30 s: node 4 is switched off"
t.getNode(4).turnOff()
run_until(50)

r.remove(1, 2) deletes the link from 1 to 2 in TOSSIM's radio model; nothing in the motes is told. They must find out the way real motes do, from advertisements that stop arriving.

Step 4: the tables as they form

$ make micaz sim 2>&1 | tail -1
*** Successfully built micaz TOSSIM library.
$ python2 run.py $TOSDIR/lib/tossim/noise/casino-lab.txt > dv.log
$ grep -c . dv.log
162
$ grep ' 0:0:10\.' dv.log
DEBUG (0): 0:0:10.000000010 node 0:  0:0/0  1:1/1  2:1/2  3:3/1  4:3/2  5:3/3
DEBUG (1): 0:0:10.000000110 node 1:  0:0/1  1:1/0  2:2/1  3:4/2  4:4/1  5:4/2
DEBUG (2): 0:0:10.000000210 node 2:  0:1/2  1:1/1  2:2/0  3:5/3  4:5/2  5:5/1
DEBUG (3): 0:0:10.000000310 node 3:  0:0/1  1:4/2  2:4/3  3:3/0  4:4/1  5:4/2
DEBUG (4): 0:0:10.000000410 node 4:  0:3/2  1:1/1  2:5/2  3:3/1  4:4/0  5:5/1
DEBUG (5): 0:0:10.000000510 node 5:  0:4/3  1:4/2  2:2/1  3:4/2  4:4/1  5:5/0

Ten seconds in, every table is complete and every one agrees with the picture. Read node 0's line: node 1 through 1 at cost 1, node 2 through 1 at cost 2, node 3 through 3 at cost 1, node 4 through 3 at cost 2, node 5 through 3 at cost 3. Each cost is the length of a shortest path, and the next hop is the first node on it.

The tables are consistent with each other, which is the real test of a routing protocol. Follow node 5's route to node 0: next hop 4; node 4's route to 0: next hop 3; node 3's: 0. Every chain of next hops ends at its destination, and no chain loops.

Where there is a tie, as for node 0's route to 4 (through 3 or through 1, both cost 2), the table keeps whichever it heard first, because a neighbour is adopted only if it is strictly better.

Step 5: a link fails

Everything the motes did between the cut at 15 seconds and the next change, leaving out the table lines:

$ awk '/^---- 15 s/ {a = 1} /^---- 30 s/ {a = 0} a && !/node [0-5]:  /' dv.log
---- 15 s: the link between 1 and 2 is cut
DEBUG (1): 0:0:18.250000110 node 1: to 2 via 2 cost 16, timed out
DEBUG (2): 0:0:18.500000210 node 2: to 0 via 1 cost 16, timed out
DEBUG (2): 0:0:18.500000210 node 2: to 1 via 1 cost 16, timed out
DEBUG (1): 0:0:18.509003043 node 1: to 2 via 4 cost 3, better route
DEBUG (0): 0:0:18.702743602 node 0: to 2 via 1 cost 4, cost changed
DEBUG (2): 0:0:19.479248501 node 2: to 0 via 5 cost 4, better route
DEBUG (2): 0:0:19.479248501 node 2: to 1 via 5 cost 3, better route
$ grep ' 0:0:25\.' dv.log
DEBUG (0): 0:0:25.000000010 node 0:  0:0/0  1:1/1  2:1/4  3:3/1  4:3/2  5:3/3
DEBUG (1): 0:0:25.000000110 node 1:  0:0/1  1:1/0  2:4/3  3:4/2  4:4/1  5:4/2
DEBUG (2): 0:0:25.000000210 node 2:  0:5/4  1:5/3  2:2/0  3:5/3  4:5/2  5:5/1
DEBUG (3): 0:0:25.000000310 node 3:  0:0/1  1:4/2  2:4/3  3:3/0  4:4/1  5:4/2
DEBUG (4): 0:0:25.000000410 node 4:  0:3/2  1:1/1  2:5/2  3:3/1  4:4/0  5:5/1
DEBUG (5): 0:0:25.000000510 node 5:  0:4/3  1:4/2  2:2/1  3:4/2  4:4/1  5:5/0
munotes.in99

Practical 10: Routing Table Generation and Analysis

Read it as a timeline.

15.0 to 18.25 seconds: nothing. The link was cut and for more than three seconds no table changed, because nobody knew. Node 1 still sent anything for node 2 straight to node 2, over a link that no longer existed. A route that looks good and loses everything sent along it is called a black hole, and every protocol that learns about failures by waiting for silence has one for as long as its timeout.

18.25: node 1 notices. Node 2's advertisements have stopped reaching it, and after 3.5 seconds without confirmation the route is retired. At 18.50 node 2 does the same for its routes to 0 and 1, which went through 1.

18.51: node 1 learns another way, the moment node 4's next advertisement arrives: 2 is at cost 2 from node 4, so at cost 3 from node 1.

18.70: node 0 believes its next hop. Node 0 routes to 2 through node 1, and node 1 now says cost 3, so node 0's cost becomes 4. It is worse than before and node 0 accepts it anyway: rule 1.

19.48: node 2 is reconnected through node 5, to node 0 at cost 4 and node 1 at cost 3.

By 19.5 seconds, about four and a half seconds after the cut, every table is right again. The tables at 25 seconds differ from those at 10 only in the routes that used the cut link: node 0 reaches 2 at cost 4, along 0, 1, 4, 5, 2; node 1 at cost 3; node 2 reaches 0 at cost 4 and 1 at cost 3. Nothing else moved, which is what a routing protocol should do: change what the failure affects, and only that.

munotes.in100

Practical 10: Routing Table Generation and Analysis

Step 6: a node fails, and the count to infinity

Node 4 is switched off at 30 seconds. It is on the only remaining path between the left of the network (0, 1, 3) and the right (2, 5), since the link from 1 to 2 is already gone, so the network splits in two. Every route across the gap is now impossible, and the question is how long the tables take to say so. Follow the routes to node 0:

$ awk '/^---- 30 s/ {a = 1} a && /to 0 via/' dv.log
DEBUG (5): 0:0:33.250000510 node 5: to 0 via 4 cost 16, timed out
DEBUG (5): 0:0:33.597290217 node 5: to 0 via 2 cost 5, better route
DEBUG (2): 0:0:33.778366570 node 2: to 0 via 5 cost 6, cost changed
DEBUG (5): 0:0:34.512512378 node 5: to 0 via 2 cost 7, cost changed
DEBUG (2): 0:0:34.785065179 node 2: to 0 via 5 cost 8, cost changed
DEBUG (5): 0:0:35.572998247 node 5: to 0 via 2 cost 9, cost changed
DEBUG (2): 0:0:35.841415860 node 2: to 0 via 5 cost 10, cost changed
DEBUG (5): 0:0:36.666397262 node 5: to 0 via 2 cost 11, cost changed
DEBUG (2): 0:0:36.797577353 node 2: to 0 via 5 cost 12, cost changed
DEBUG (5): 0:0:37.576553541 node 5: to 0 via 2 cost 13, cost changed
DEBUG (2): 0:0:37.882156823 node 2: to 0 via 5 cost 14, cost changed
DEBUG (5): 0:0:38.562851108 node 5: to 0 via 2 cost 15, cost changed
DEBUG (2): 0:0:38.846329213 node 2: to 0 via 5 cost 16, unreachable
DEBUG (5): 0:0:39.609817661 node 5: to 0 via 2 cost 16, unreachable
$ grep ' 0:0:45\.' dv.log
DEBUG (0): 0:0:45.000000010 node 0:  0:0/0  1:1/1  2:-/-  3:3/1  4:-/-  5:-/-
DEBUG (1): 0:0:45.000000110 node 1:  0:0/1  1:1/0  2:-/-  3:0/2  4:-/-  5:-/-
DEBUG (2): 0:0:45.000000210 node 2:  0:-/-  1:-/-  2:2/0  3:-/-  4:-/-  5:5/1
DEBUG (3): 0:0:45.000000310 node 3:  0:0/1  1:0/2  2:-/-  3:3/0  4:-/-  5:-/-
DEBUG (5): 0:0:45.000000510 node 5:  0:-/-  1:-/-  2:2/1  3:-/-  4:-/-  5:5/0

33.25: node 5 notices. Its route to 0 went through node 4, which has been silent for 3.25 seconds, so it is retired: cost 16.

33.60: node 5 is deceived. Node 2 advertises that it can reach node 0 at cost 4, which was true a moment ago, because node 2's route to 0 goes through node 5 itself (Step 5 left it there). Node 5 does not know that. Cost 4 plus 1 is 5, and anything beats 16, so node 5 now routes to 0 through node 2, and node 2 routes to 0 through node 5. Two nodes pointing at each other is a routing loop: a packet for node 0 would bounce between them until it died.

munotes.in101

Practical 10: Routing Table Generation and Analysis

33.78 to 39.61: counting to infinity. Node 2 hears its next hop, node 5, say cost 5, and by rule 1 sets its own cost to 6. Node 5 hears its next hop, node 2, say 6, and sets 7. Each advertisement adds one: 8, 9, 10, 11, 12, 13, 14, 15, and at 16 both finally agree that node 0 is unreachable, node 2 at 38.85 seconds and node 5 at 39.61. For over six seconds after it lost its path to node 0, node 5 believed it had one.

That is why "infinity" must be a small number. RIP's specification, RFC 2453, uses the same 16: "16 is used. This value is normally referred to as 'infinity'", and it states the price: RIP "is limited to networks whose longest path (the network's diameter) is 15 hops." A larger infinity would allow larger networks and make every count like this one longer.

On the left side the tables are right: nodes 1 and 3 now reach each other through 0 at cost 2, since the path through 4 is gone, and everything on the right side is unreachable from the left. Node 4 prints nothing: it is off.

Step 7: split horizon

$ sed -i 's/SPLIT_HORIZON = 0/SPLIT_HORIZON = 1/' DV.h
$ make micaz sim 2>&1 | tail -1
*** Successfully built micaz TOSSIM library.
$ python2 run.py $TOSDIR/lib/tossim/noise/casino-lab.txt > sh.log
$ awk '/^---- 30 s/ {a = 1} a && /to 0 via/' sh.log
DEBUG (5): 0:0:33.250000510 node 5: to 0 via 4 cost 16, timed out
DEBUG (2): 0:0:33.778366570 node 2: to 0 via 5 cost 16, unreachable
$ diff <(grep ' 0:0:25\.' dv.log) <(grep ' 0:0:25\.' sh.log) && echo "tables at 25 s: identical"
tables at 25 s: identical

Two lines instead of fourteen. Node 5 retired its route to 0 at 33.25, as before. Node 2's advertisement still offered node 0 at cost 4, but it also said the route went via 5, and with split horizon node 5 ignores a route that goes through itself: it is no use to the node it would lead back to. So node 5 stayed at 16, node 2 heard its next hop say 16, and at 33.78 node 0 was unreachable from node 2 as well: half a second after the timeout instead of six. And the tables at 25 seconds are identical with and without it: split horizon changes nothing while the network is healthy.

This is the idea RFC 2453 calls split horizon with poisoned reverse: a route learned from a neighbour is, as far as that neighbour is concerned, unreachable. RIP does it by advertising such routes to that neighbour with cost 16; here, because every advertisement is a broadcast, the sender says whom each route goes through and the receiver applies the rule.

munotes.in102

Practical 10: Routing Table Generation and Analysis

It has a limit, and the RFC states it: it "will prevent any routing loops that involve only two routers. However, it is still possible to end up with patterns in which three routers are engaged in mutual deception." The loop here was between two nodes, so split horizon was enough. The RFC's further remedy is triggered updates, advertising a change at once instead of waiting for the next regular advertisement; an earlier version of this program aged its routes just before each advertisement, which amounts to the same thing, and it never counted to infinity in this network at all.

Procedure

  1. Write a distance-vector routing module: advertise the table about once a second, learn from neighbours' advertisements, age routes not confirmed for 3.5 seconds, and print the table every 5 seconds.
  2. Build a six-node network in TOSSIM from a list of links.
  3. Run 50 simulated seconds, cutting one link at 15 seconds and switching one node off at 30.
  4. Read the converged tables at 10 seconds.
  5. Read the route changes after the link cut and the tables at 25 seconds.
  6. Read the route changes after the node failure, and find the count to infinity.
  7. Turn split horizon on, rebuild, run the same scenario and compare.

Observations

EventWhat the tables didWhen
Startevery table complete and consistentby 10 s
Link 1-2 cutnothing, for 3.25 s: a black hole15.00 to 18.25 s
node 1 and node 2 retire their routes over the cut link18.25, 18.50 s
new routes found through 4 and 5; node 0 accepts a worse cost from its next hop18.51 to 19.48 s
converged, only the affected routes changedabout 4.5 s after the cut
Node 4 off, no split horizonnode 5 retires its route to 0, then adopts node 2's stale route33.25, 33.60 s
nodes 2 and 5 count 5, 6, ... 1633.78 to 39.61 s
Node 4 off, split horizonnode 5 retires the route; node 2 follows33.25, 33.78 s

Result

A distance-vector routing protocol was implemented in nesC: each node advertises its cost and next hop to every destination about once a second, adopts a neighbour's route when it is strictly better, believes its current next hop even when it reports a worse cost, and retires a route not confirmed for 3.5 seconds. On six motes in TOSSIM the routing tables were complete and consistent within 10 seconds. When a link was cut, the tables stayed wrong for 3.25 seconds until the routes timed out, and then converged about 4.5 seconds after the cut, changing only the routes that had used the link. When a node failed and split the network, two nodes adopted each other's stale routes and counted their cost up by one per advertisement from 5 to the infinity of 16, taking over six seconds to agree the destination was unreachable. With split horizon, which ignores a route that goes through the receiver, the same failure was resolved in half a second, and the tables in normal operation were unchanged.

munotes.in103

Practical 10: Routing Table Generation and Analysis

Where marks are lost

Adopting only better routes. A route must follow its next hop's news even when it gets worse, or a node keeps a cost that is no longer true.

Never retiring routes. Without a timeout a dead neighbour's routes live for ever.

Calling the time before the timeout "convergence". It is a black hole: routes that look good and lose every packet.

Choosing a large infinity. Counting to infinity takes one advertisement per step; 16 limits the network to 15 hops for exactly that reason.

Claiming split horizon prevents all loops. It prevents loops of two nodes; RFC 2453 says loops of three can still count to infinity.

Naming a constant INFINITY. It is a C macro from math.h, and the build fails.

Advertising at exactly the same moment on every node. Synchronised broadcasts collide; add jitter.

Reading a summary instead of the log. Check any count against the lines it came from.

For the journal

Aim; routing tables and distance vector, with the three rules; the network drawn; DV.h, DistanceVectorC.nc, DistanceVectorAppC.nc, the Makefile and run.py; the tables at 10 seconds with one route followed hop by hop; the changes after the link cut and the tables at 25 seconds; the count to infinity after the node failure; the split-horizon run; the observation table; the result.

Quick revision

  • A routing table: destination, next hop, cost.
  • Distance vector: advertise your vector; cost through a neighbour is its cost plus one; keep the cheapest.
  • Believe your next hop even when it gets worse; retire routes that are not confirmed; 16 means unreachable.
  • Converged here in 10 s; a cut link was fixed about 4.5 s later, after a 3.25 s black hole.
  • A failure that splits the network made nodes 2 and 5 count 5 to 16 in about six seconds.
  • RFC 2453: infinity is 16, so RIP networks are limited to 15 hops.
  • Split horizon (with poisoned reverse): a route through the receiver is useless to it; stops two-node loops, not three-node loops.
  • Triggered updates, sending news at once, shorten convergence further.
  • TinyOS's own routing for sensor networks is collection towards a sink, CTP (TEP 123).
munotes.in104

Practical 10: Routing Table Generation and Analysis

Questions you must be able to answer

1. What does a routing table hold? One row per destination: the neighbour to send through, the next hop, and the cost to reach the destination, here in hops.

2. How does distance vector build the tables? Each node repeatedly broadcasts its costs to every destination. A node that hears a neighbour offer destination D at cost c records D at c + 1 through that neighbour if that is cheaper than what it has, and always believes the neighbour it already routes through.

3. The link between 1 and 2 was cut at 15 seconds and nothing changed until 18.25. Why, and what is that period called? The motes learn of a failure only when a route has gone unconfirmed for the timeout, 3.5 seconds. Until then packets would be sent over a link that no longer exists: a black hole.

4. Node 0's cost to node 2 went from 2 to 4. Why did it accept a worse cost? Because node 1 was its next hop for node 2, and a node must believe its next hop's news: node 1 really could now reach 2 only at cost 3.

5. What is counting to infinity, and where did it happen here? Two nodes that have lost a destination each route to it through the other and raise each other's cost one advertisement at a time until it reaches infinity. Nodes 2 and 5 did it for node 0, from cost 5 to 16, between 33.6 and 39.6 seconds.

6. Why is infinity 16 in RIP? Because counting to it takes one round per step, so it must be small; RFC 2453 accepts the consequence that RIP networks can be at most 15 hops across.

7. What is split horizon, and what did it do here? A node does not accept a route that its neighbour reaches through the node itself. Node 5 ignored node 2's route to 0 because it went through node 5, so the count to infinity never started and node 0 was marked unreachable within half a second.

8. Can split horizon prevent every loop? No. It prevents loops between two nodes; RFC 2453 notes that three routers can still deceive each other in a cycle, which only reaching infinity or triggered updates resolve.

Contents This chapter on its own page

munotes.in105

Module II

NS-2 and the network: MANET routing with AODV and DSR, CSMA/CA, TDMA and duty-cycling MACs, directional antennas, coverage, and a cellular request

munotes.in

Chapter Thirteen

The Network Simulator Laboratory from Zero: NS-2, Tcl, the Trace File and NAM

Syllabus topic Module 2, with the paper's own description: "Using tools such as TinyOS, nesC, TOSSIM, and network simulators", and CO 2: "To develop skills in simulating wireless sensor and ad-hoc networks using TOSSIM and network simulation tools."

Aim

To install the NS-2 network simulator and its animator NAM on Ubuntu, to write, run and read a wired and a wireless simulation, and to measure a simulated network from its trace file with awk.

What you need to know before you start

A network simulator runs a model of a network instead of a real one: nodes, links or radio channels, protocols at every layer, traffic, and time. Like TOSSIM it is a discrete-event simulator (Practical 6): a queue of timed events and a clock that jumps from one to the next. Unlike TOSSIM it does not run your mote's program. It runs its own implementations of real protocols, such as 802.11, AODV and DSR, and you describe the network they run in.

NS-2 is the classic network simulator of research and teaching. Its protocols are written in C++ for speed, and you drive it from OTcl, an object-oriented Tcl: you write a Tcl script that creates the simulator, builds the network, schedules the traffic and says what to record, and the command ns runs it. Ubuntu packages version 2.35. NS-3 is a separate simulator, not a newer version of this one: its scripts are C++ or Python, and it does not run NS-2 scripts.

Three kinds of output come out of a simulation, and each of the next ten practicals uses them:

OutputWhat it isHow it is read
the trace file (.tr)one line per packet event: sent, received, forwarded, droppedwith awk, to measure the network
the NAM file (.nam)positions, links and packet movements, for the animatorwith nam, to watch the network
puts in the scriptanything the script printson the terminal

Step 1: installing NS-2 and NAM

$ sudo apt update
$ sudo apt install ns2 nam tcl8.6

ns2 gives the ns command and nam the animator. tcl8.6 is worth installing with them. NS-2 was built expecting the Tcl shell at /usr/bin/tclsh8.6, and when it is missing, ns prints this at the start of every run before carrying on:

When configured, ns found the right version of tclsh in /usr/bin/tclsh8.6
but it doesn't seem to be there anymore, so ns will fall back on running the first tclsh in your path.

Check that both commands exist and that ns runs a script:

$ which ns nam
/usr/bin/ns
/usr/bin/nam
$ printf 'puts "NS-2 is working"\n' > check.tcl
$ ns check.tcl
NS-2 is working

Step 2: the Tcl a script needs

An NS-2 script is a Tcl program, and a handful of Tcl commands carry every script in this book. ns runs plain Tcl too, so try them on their own first. hello.tcl:

# hello.tcl: the Tcl an NS-2 script needs
set nodes 3                          ;# a variable: set name value
set range 250.0
puts "nodes: $nodes, range: $range m"
set side [expr $range * 2]           ;# [ ] runs a command and is replaced by its result
puts "side of the area: $side"
proc square {x} {                    ;# a procedure with one argument
    return [expr $x * $x]
}
puts "square of 12: [square 12]"
for {set i 0} {$i < $nodes} {incr i} {
    set pos($i) [expr $i * 200]      ;# an array element: pos(0), pos(1), pos(2)
    puts "node $i at x = $pos($i)"
}
if {$nodes > 2} {
    puts "a multi-hop network is possible"
}
munotes.in106

The Network Simulator Laboratory from Zero: NS-2, Tcl, the Trace File and NAM

$ ns hello.tcl
nodes: 3, range: 250.0 m
side of the area: 500.0
square of 12: 144
node 0 at x = 0
node 1 at x = 200
node 2 at x = 400
a multi-hop network is possible
TclMeaning
set x 5make or change a variable
$xits value
[cmd args]run a command and put its result here
exprarithmetic
proc name {args} {body}define a procedure
for, if, incrloops, conditions, add one
;#a comment after a command on the same line
"..." and {...}a string in which $ and [ ] are replaced, and one taken literally

OTcl adds objects to this. new Simulator makes an object and returns its name; $ns node calls the method node on the object whose name is in ns. That is all the object orientation a script needs.

Step 3: the smallest simulation

Two nodes joined by a wire, and one flow of packets from the first to the second. The source is made to offer a little more than the link can carry, so that every kind of trace event appears. wired.tcl:

# wired.tcl: two nodes, one link, one UDP flow that offers more than the link carries
set ns [new Simulator]
set tf [open wired.tr w]
$ns trace-all $tf
set nf [open wired.nam w]
$ns namtrace-all $nf

set n0 [$ns node]
set n1 [$ns node]
$ns duplex-link $n0 $n1 1Mb 10ms DropTail
$ns queue-limit $n0 $n1 10

set udp [new Agent/UDP]
$ns attach-agent $n0 $udp
set sink [new Agent/Null]
$ns attach-agent $n1 $sink
$ns connect $udp $sink

set cbr [new Application/Traffic/CBR]
$cbr set packetSize_ 1000
$cbr set rate_ 1.2Mb
$cbr attach-agent $udp

$ns at 0.5 "$cbr start"
$ns at 2.5 "$cbr stop"
$ns at 3.0 "finish"

proc finish {} {
    global ns tf nf
    $ns flush-trace
    close $tf
    close $nf
    exit 0
}
$ns run
LineWhat it does
set ns [new Simulator]creates the simulator; every other command goes through $ns
$ns trace-all $tfrecords every packet event on every link in wired.tr
$ns namtrace-all $nfrecords the same for the animator in wired.nam
$ns nodemakes a node
$ns duplex-link $n0 $n1 1Mb 10ms DropTaila link in both directions: 1 Mb/s, 10 ms of propagation delay, and a drop-tail queue at each end
$ns queue-limit $n0 $n1 10the size of the queue from n0 to n1; step 4 shows what 10 means exactly
Agent/UDP, Agent/Nullthe transport at each end: UDP sends, Null receives and throws the packets away
$ns connect $udp $sinktells the UDP agent where to send
Application/Traffic/CBRconstant bit rate traffic: packets of packetSize_ bytes at rate_
$ns at 0.5 "$cbr start"schedules an event; a script is mostly a list of these
proc finishflushes and closes the files, and ends the program
$ns runstarts the clock; nothing happens before it
munotes.in107

The Network Simulator Laboratory from Zero: NS-2, Tcl, the Trace File and NAM

A run prints nothing on the terminal. Everything went into the two files:

$ ns wired.tcl
$ wc -l wired.tr wired.nam
   859 wired.tr
  1124 wired.nam
  1983 total

Step 4: reading a wired trace

$ head -12 wired.tr
+ 0.5 0 1 cbr 1000 ------- 0 0.0 1.0 0 0
- 0.5 0 1 cbr 1000 ------- 0 0.0 1.0 0 0
+ 0.506667 0 1 cbr 1000 ------- 0 0.0 1.0 1 1
- 0.508 0 1 cbr 1000 ------- 0 0.0 1.0 1 1
+ 0.513333 0 1 cbr 1000 ------- 0 0.0 1.0 2 2
- 0.516 0 1 cbr 1000 ------- 0 0.0 1.0 2 2
r 0.518 0 1 cbr 1000 ------- 0 0.0 1.0 0 0
+ 0.52 0 1 cbr 1000 ------- 0 0.0 1.0 3 3
- 0.524 0 1 cbr 1000 ------- 0 0.0 1.0 3 3
r 0.526 0 1 cbr 1000 ------- 0 0.0 1.0 1 1
+ 0.526667 0 1 cbr 1000 ------- 0 0.0 1.0 4 4
- 0.532 0 1 cbr 1000 ------- 0 0.0 1.0 4 4

Every line has the same twelve fields, written by Trace::format() in the simulator's C++ (ns Manual, section 26.4). The first line, field by field:

FieldHereMeaning
1+the event: + entered the queue, - left the queue onto the link, r received at the far end, d dropped
20.5the time, in seconds
30the node at the start of the link
41the node at the end of the link
5cbrthe packet type
61000the size, in bytes
7-------seven flags, a dash where a flag is not set; they are for TCP's congestion signals and packet priority
80the flow id
90.0the source, as node.port
101.0the destination, as node.port
110the sequence number
120the packet's unique id, which follows it through the whole trace
munotes.in108

The Network Simulator Laboratory from Zero: NS-2, Tcl, the Trace File and NAM

Now read the twelve lines as a story. Packet 0 enters the queue at 0.5 s and leaves it at once, because the link is idle. It arrives at node 1 at 0.518 s: a 1000-byte packet is 1000 × 8 = 8000 bits, which take 8 ms to put on a 1 Mb/s link, and the last bit then needs the link's 10 ms to arrive, so 0.5 + 0.008 + 0.010 = 0.518. Packet 1 enters the queue at 0.506667 s but cannot leave until the link is free, at 0.508 s. From here on the link sends a packet every 8 ms, while the source adds one every 6.667 ms.

Count the events by type:

$ awk '{print $1}' wired.tr | sort | uniq -c
    300 +
    259 -
     41 d
    259 r

Every packet the source made entered the queue: 300. Of them 259 left it and 259 arrived, and 41 were dropped: 259 + 41 = 300. The numbers follow from three facts.

The source offers 150 packets a second. A CBR source sends one packet of packetSize_ bytes every packetSize_ × 8 / rate_ seconds (tools/cbr_traffic.cc, line 90): 1000 × 8 / 1200000 = 1 / 150 of a second. In the two seconds from 0.5 to 2.5 that is 150 × 2 = 300 packets.

The link carries 125 packets a second, one every 8 ms: 1000000 / 8000 = 125. So the queue grows by 150 - 125 = 25 packets every second.

The queue holds nine, not ten. DropTail::enque() drops an arriving packet when the queue's length plus one would reach the limit (queue/drop-tail.cc, line 92). With a limit of 10, a packet that arrives to find nine waiting is dropped. The packet being sent is on the link, not in the queue.

So the nine places fill about 9 / 25 = 0.36 seconds after the start, and the first drop should come near 0.86 s; from then on, 25 packets a second are dropped until the source stops at 2.5 s. When it stops, the nine packets waiting are still sent. The link sent 125 × 2 = 250 packets while the source ran, and 250 + 9 = 259.

Step 5: the wireless skeleton

A wireless simulation needs more than a wired one, because a radio node is built from parts: a link layer, a MAC, a network interface, an antenna and a channel shared with every other node. NS-2 calls such a node a mobile node and builds each one from a list of parts you give once with node-config. wireless.tcl puts three nodes in a line, 200 m apart, runs AODV, and sends a packet every half second from node 0 to node 2:

munotes.in109

The Network Simulator Laboratory from Zero: NS-2, Tcl, the Trace File and NAM

# wireless.tcl: three mobile nodes in a line, AODV, one CBR flow from node 0 to node 2
set val(chan)   Channel/WirelessChannel
set val(prop)   Propagation/TwoRayGround
set val(netif)  Phy/WirelessPhy
set val(mac)    Mac/802_11
set val(ifq)    Queue/DropTail/PriQueue
set val(ll)     LL
set val(ant)    Antenna/OmniAntenna
set val(ifqlen) 50
set val(nn)     3
set val(rp)     AODV
set val(x)      500
set val(y)      200
set val(stop)   10.0

set ns [new Simulator]
set tf [open wireless.tr w]
$ns trace-all $tf
set nf [open wireless.nam w]
$ns namtrace-all-wireless $nf $val(x) $val(y)

set topo [new Topography]
$topo load_flatgrid $val(x) $val(y)
create-god $val(nn)

$ns node-config -adhocRouting $val(rp) \
    -llType $val(ll) \
    -macType $val(mac) \
    -ifqType $val(ifq) \
    -ifqLen $val(ifqlen) \
    -antType $val(ant) \
    -propType $val(prop) \
    -phyType $val(netif) \
    -channel [new $val(chan)] \
    -topoInstance $topo \
    -agentTrace ON \
    -routerTrace ON \
    -macTrace OFF \
    -movementTrace OFF

for {set i 0} {$i < $val(nn)} {incr i} {
    set node($i) [$ns node]
    $node($i) random-motion 0
    $node($i) set X_ [expr 50 + $i * 200]
    $node($i) set Y_ 100
    $node($i) set Z_ 0
    $ns initial_node_pos $node($i) 30
}

set udp [new Agent/UDP]
$ns attach-agent $node(0) $udp
set sink [new Agent/Null]
$ns attach-agent $node(2) $sink
$ns connect $udp $sink
set cbr [new Application/Traffic/CBR]
$cbr set packetSize_ 512
$cbr set interval_ 0.5
$cbr attach-agent $udp
$ns at 1.0 "$cbr start"
$ns at 9.0 "$cbr stop"

for {set i 0} {$i < $val(nn)} {incr i} {
    $ns at $val(stop) "$node($i) reset"
}
$ns at $val(stop) "finish"
proc finish {} {
    global ns tf nf
    $ns flush-trace
    close $tf
    close $nf
    exit 0
}
$ns run

The parts, as node-config chooses them:

OptionValue hereWhat it chooses
-adhocRoutingAODVthe routing protocol: AODV, DSR, DSDV and others
-llTypeLLthe link layer, which also runs ARP
-macTypeMac/802_11the medium access control: IEEE 802.11
-ifqTypeQueue/DropTail/PriQueuethe interface queue; it puts routing packets at the head of the queue, ahead of data (queue/priqueue.cc)
-ifqLen50its length, in packets
-antTypeAntenna/OmniAntennaan antenna that radiates equally in every direction
-propTypePropagation/TwoRayGroundhow the received power falls with distance
-phyTypePhy/WirelessPhythe radio: transmit power, thresholds, frequency
-channel[new Channel/WirelessChannel]the channel every node transmits on
-topoInstance$topothe flat area the nodes live in
-agentTrace and the restON, ON, OFF, OFFwhich layers write to the trace: agents, routing, MAC, movement

Four more lines are new. Topography with load_flatgrid makes a flat area 500 m by 200 m. create-god makes God, one object for the whole simulation that is given the number of nodes and uses it "to create a matrix to store connectivity information" (ns Manual, section 16.5): how many hops separate each pair of nodes. Mobile nodes cannot be made without it. namtrace-all-wireless tells NAM the size of the area. And random-motion 0 keeps a node still unless the script moves it.

munotes.in110

The Network Simulator Laboratory from Zero: NS-2, Tcl, the Trace File and NAM

Unlike the wired run, this one talks:

$ ns wireless.tcl
num_nodes is set 3
INITIALIZE THE LIST xListHead
channel.cc:sendUp - Calc highestAntennaZ_ and distCST_
highestAntennaZ_ = 1.5,  distCST_ = 550.0
SORTING LISTS ...DONE!
LinePrinted byMeaning
num_nodes is set 3mobile/god.cc, line 918God has been told how many nodes there are
INITIALIZE THE LIST xListHeadmac/channel.cc, line 401the channel starts its list of the nodes on it
highestAntennaZ_ = 1.5, distCST_ = 550.0mac/channel.cc, line 331, at the first transmissionthe carrier-sense distance: a node more than about 550 m from the sender is not given the packet at all
SORTING LISTS ...DONE!mac/channel.cc, line 445the channel sorts that list by x coordinate, so it can find the nodes near a sender quickly

These lines are the same on every wireless run in this book. Only the node count changes.

Why 250 m and 550 m

NS-2's radio has two thresholds. A packet arriving above RXThresh_ is received. One arriving between CSThresh_ and RXThresh_ is detected but cannot be decoded: the receiver marks it as an error, and its only effect is that the channel is busy (mac/wireless-phy.cc, lines 346 to 358). Below CSThresh_ it is not even detected. With the two-ray ground model, the power received at a distance d beyond a crossover distance is Pt × Gt × Gr × ht² × hr² / d⁴ (mobile/tworayground.cc, line 79). The defaults turn the two thresholds into two distances:

# range.py: the distances NS-2's default radio thresholds stand for
Pt = 0.28183815            # transmit power, watts (Phy/WirelessPhy Pt_)
Gt = Gr = 1.0              # antenna gains (Antenna/OmniAntenna Gt_, Gr_)
ht = hr = 1.5              # antenna heights, metres (Antenna/OmniAntenna Z_)
L = 1.0                    # system loss (Phy/WirelessPhy L_)
wavelength = 3e8 / 914e6   # metres, at freq_ 914 MHz

def two_ray(d):
    """Received power at distance d beyond the crossover distance."""
    return Pt * Gt * Gr * ht ** 2 * hr ** 2 / (d ** 4 * L)

def distance(threshold):
    """The distance at which the received power falls to the threshold."""
    return (Pt * Gt * Gr * ht ** 2 * hr ** 2 / (threshold * L)) ** 0.25

print("crossover distance    %6.1f m" % (4 * 3.14159265 * ht * hr / wavelength))
print("receive range         %6.1f m  (RXThresh_ 3.652e-10 W)" % distance(3.652e-10))
print("carrier-sense range   %6.1f m  (CSThresh_ 1.559e-11 W)" % distance(1.559e-11))
print("power at 200 m        %.3e W" % two_ray(200))
print("power at 400 m        %.3e W" % two_ray(400))
munotes.in111

The Network Simulator Laboratory from Zero: NS-2, Tcl, the Trace File and NAM

$ python3 range.py
crossover distance      86.1 m
receive range          250.0 m  (RXThresh_ 3.652e-10 W)
carrier-sense range    550.0 m  (CSThresh_ 1.559e-11 W)
power at 200 m        8.918e-10 W
power at 400 m        5.573e-11 W
The three nodes of wireless.tcl on their line, at 50, 250 and 450 metres. From node 0, a band out to 250 metres is marked "receives" and a band from 250 to 550 metres "senses only". Node 1, 200 metres away, is received; node 2, 400 metres away, is sensed but not received.

Figure 13.1 What node 0 can receive and sense, with NS-2's default radio

So in the default NS-2 a node receives what is sent within 250 m and senses what is sent within 550 m. In wireless.tcl the neighbours are 200 m apart, above the receive threshold; nodes 0 and 2 are 400 m apart, below it but above the carrier-sense threshold. Node 0 cannot talk to node 2, but it can hear that node 2 is talking. Every MANET in this book is laid out with these two numbers in mind, and Practical 15 changes them on purpose.

Step 6: reading a wireless trace

A mobile node's trace is written by different code, trace/cmu-trace.cc, in a different format. Unless a script calls $ns use-newtrace, NS-2 writes the older format, which is the one most books and most awk scripts expect:

$ head -15 wireless.tr
s 1.000000000 _0_ AGT  --- 0 cbr 512 [0 0 0 0] ------- [0:0 2:0 32 0] [0] 0 0
r 1.000000000 _0_ RTR  --- 0 cbr 512 [0 0 0 0] ------- [0:0 2:0 32 0] [0] 0 0
s 1.000000000 _0_ RTR  --- 0 AODV 48 [0 0 0 0] ------- [0:255 -1:255 30 0] [0x2 1 1 [2 0] [0 4]] (REQUEST)
r 1.001408667 _1_ RTR  --- 0 AODV 48 [0 ffffffff 0 800] ------- [0:255 -1:255 30 0] [0x2 1 1 [2 0] [0 4]] (REQUEST)
s 1.001550713 _1_ RTR  --- 0 AODV 48 [0 ffffffff 0 800] ------- [1:255 -1:255 29 0] [0x2 2 1 [2 0] [0 4]] (REQUEST)
r 1.002799380 _0_ RTR  --- 0 AODV 48 [0 ffffffff 1 800] ------- [1:255 -1:255 29 0] [0x2 2 1 [2 0] [0 4]] (REQUEST)
r 1.002799380 _2_ RTR  --- 0 AODV 48 [0 ffffffff 1 800] ------- [1:255 -1:255 29 0] [0x2 2 1 [2 0] [0 4]] (REQUEST)
s 1.002799380 _2_ RTR  --- 0 AODV 44 [0 0 0 0] ------- [2:255 0:255 30 1] [0x4 1 [2 4] 10.000000] (REPLY)
r 1.007342047 _1_ RTR  --- 0 AODV 44 [13a 1 2 800] ------- [2:255 0:255 30 1] [0x4 1 [2 4] 10.000000] (REPLY)
f 1.007342047 _1_ RTR  --- 0 AODV 44 [13a 1 2 800] ------- [2:255 0:255 29 0] [0x4 2 [2 4] 10.000000] (REPLY)
r 1.011993713 _0_ RTR  --- 0 AODV 44 [13a 0 1 800] ------- [2:255 0:255 29 0] [0x4 2 [2 4] 10.000000] (REPLY)
s 1.011993713 _0_ RTR  --- 0 cbr 532 [0 0 0 0] ------- [0:0 2:0 30 1] [0] 0 0
r 1.017795713 _1_ RTR  --- 0 cbr 532 [13a 1 0 800] ------- [0:0 2:0 30 1] [0] 1 0
f 1.017795713 _1_ RTR  --- 0 cbr 532 [13a 1 0 800] ------- [0:0 2:0 29 2] [0] 1 0
r 1.024077713 _2_ AGT  --- 0 cbr 532 [13a 2 1 800] ------- [0:0 2:0 29 2] [0] 2 0
munotes.in112

The Network Simulator Laboratory from Zero: NS-2, Tcl, the Trace File and NAM

The thirteenth line, r 1.017795713 _1_ RTR --- 0 cbr 532 [13a 1 0 800] ------- [0:0 2:0 30 1] [0] 1 0, field by field, from the sprintf calls that write it:

FieldHereMeaning
1rthe event: s sent, r received, f forwarded, D dropped
21.017795713the time, in seconds, to the nanosecond
3_1_the node where it happened, between underscores
4RTRthe layer that traced it: AGT the agent, RTR the routing agent, MAC, IFQ the interface queue
5---the reason, for a drop: NRTE no route, COL collision, IFQ queue full, END end of simulation, and others
60the packet's unique id
7cbrthe packet type
8532the size, in bytes
9[13a 1 0 800]the MAC header, in hex: the duration the frame reserves the medium for, the receiver's MAC address, the transmitter's MAC address, and the type (800 is IP, 806 is ARP)
10-------flags, unused here
11[0:0 2:0 30 1]the IP header: source node:port, destination node:port, TTL, next hop
12[0] 1 0the CBR data: sequence number, how many times it has been forwarded so far, and God's shortest hop count, which is 0 unless the script gives God the distances (Practical 11's scenario file does)

Five things in the fifteen lines are worth seeing once.

Route discovery comes first. The first data packet has nowhere to go: node 0 has no route to node 2. AODV holds the packet and broadcasts a route REQUEST; node 1 rebroadcasts it; node 2, the destination, answers with a REPLY, which node 1 forwards back. Only then, at 1.012 s, does the data leave. Practical 12 reads these packets field by field.

The size grows by 20 bytes at the routing layer. The agent sends 512 bytes, and the packet goes out at 532, because AODV adds the IP header's 20 bytes to a packet its own node originates (aodv/aodv.cc, line 582).

The TTL is 32, then 30, then 29. 32 is NS-2's default. AODV sets it to 30 on a packet it originates, its NETWORK_DIAMETER of 30 hops (aodv/aodv.h, line 98), and each hop takes one off.

munotes.in113

The Network Simulator Laboratory from Zero: NS-2, Tcl, the Trace File and NAM

A broadcast and a unicast look different in the MAC field. The rebroadcast request shows [0 ffffffff 0 800]: no duration, and the all-ones broadcast address as the receiver. The data shows [13a 1 0 800]: received by node 1, sent by node 0. 13a is hexadecimal: (13A)₁₆ = 314 microseconds, the time the frame reserves the medium for its acknowledgement. That is the 10-microsecond gap 802.11 leaves before an acknowledgement, SIFS, plus the acknowledgement itself: a 14-byte ACK behind the 24-byte physical preamble and header, 38 × 8 = 304 bits at 1 Mb/s, so 304 + 10 = 314. (The ns Manual, section 16.1.6, reads its own example's a2 as "160+2 sec". It is 162 microseconds.)

A forward is traced twice. Node 1 logs r when the packet arrives at its routing agent and f when it sends it on, with the TTL one lower and the next hop now 2. The rebroadcast request is logged as s, not f, because node 1 sends it with its own address as the IP source.

Step 7: measuring with awk

The trace is the raw data. Every number a practical asks for, throughput, delivery ratio, delay, overhead, is counted out of it with awk. awk reads a file a line at a time, splits each line into fields $1, $2 and so on, and runs every pattern { action } whose pattern is true; END runs once, after the last line.

For the wired run, wired.awk:

# wired.awk: what happened on the link from node 0 to node 1
$1 == "+" { offered++ }
$1 == "d" { dropped++; if (first_drop == "") first_drop = $2 }
$1 == "r" { received++ }
$1 == "r" && $2 >= 1.0 && $2 < 2.0 { window += $6 * 8 }
$1 == "-" && $2 >= 2.5 { after_stop++ }
END {
    printf "offered to the link    %d packets\n", offered
    printf "dropped at the queue   %d packets, the first at %s s\n", dropped, first_drop
    printf "received by node 1     %d packets\n", received
    printf "sent after the stop    %d packets, from the queue\n", after_stop
    printf "throughput, 1 to 2 s   %.1f kbit/s\n", window / 1000
}
$ awk -f wired.awk wired.tr
offered to the link    300 packets
dropped at the queue   41 packets, the first at 0.866667 s
received by node 1     259 packets
sent after the stop    9 packets, from the queue
throughput, 1 to 2 s   1000.0 kbit/s

Everything step 4 predicted is there. The first drop comes at 0.866667 s, 0.366667 s after the start, where the reasoning gave about 0.36. The nine packets waiting when the source stopped were still sent. And in the one second from 1 to 2 s, when the queue was always full, node 1 received exactly 1000 kbit: throughput is what arrives per second, and a full link delivers exactly its capacity, however much more is offered. Throughput is measured over a window in which the flow is steady; measured from the first packet to the last it would include the start and the end, and come out a little different every time.

munotes.in114

The Network Simulator Laboratory from Zero: NS-2, Tcl, the Trace File and NAM

For the wireless run, wireless.awk counts the data packets the application sent and received, matches them by their unique id to time each one, and counts the routing packets:

# wireless.awk: delivery, delay and routing packets from an old-format wireless trace
$1 == "s" && $4 == "AGT" && $7 == "cbr" { sent++; start[$6] = $2 }
$1 == "r" && $4 == "AGT" && $7 == "cbr" { got++; delay += $2 - start[$6] }
$4 == "RTR" && $7 == "AODV" && ($1 == "s" || $1 == "f") { routing++ }
END {
    printf "data packets sent       %d\n", sent
    printf "data packets received   %d\n", got
    printf "packet delivery ratio   %.1f per cent\n", got * 100 / sent
    printf "average delay           %.2f ms\n", delay / got * 1000
    printf "routing packets sent    %d\n", routing
}
$ awk -f wireless.awk wireless.tr
data packets sent       16
data packets received   16
packet delivery ratio   100.0 per cent
average delay           12.55 ms
routing packets sent    4
MeasureFormulaHere
packet delivery ratiodata packets received by the destination's agent ÷ data packets sent by the source's agent16 of 16, 100 per cent
end-to-end delaythe mean of (time received at the destination's agent - time sent by the source's agent)12.55 ms
routing packetsrouting packets sent or forwarded, by every node4: a request, its rebroadcast, a reply, its forward

The delay is an average over a packet that waited for a route and fifteen that did not. The first took 1.024077713 - 1.0 = 0.024077713 s, the others about 11.7 ms each. Practical 14 separates the two.

Why the patterns name AGT. A data packet appears as r at every routing agent it passes, including its own node's. Counting every received cbr line would count each packet three times here:

$ awk '$1 == "r" && $7 == "cbr"' wireless.tr | wc -l
48
$ awk '$1 == "r" && $4 == "AGT" && $7 == "cbr"' wireless.tr | wc -l
16

A delivery ratio of 48 / 16, 300 per cent, is the mark of that mistake.

munotes.in115

The Network Simulator Laboratory from Zero: NS-2, Tcl, the Trace File and NAM

Step 8: the NAM file

The NAM file describes the same run for the animator. Its lines begin with a letter for the kind of record, and every value after it is a flag and a value:

$ head -12 wired.nam
V -t * -v 1.0a5 -a 0
A -t * -n 1 -p 0 -o 0x7fffffff -c 30 -a 1
A -t * -h 1 -m 1073741823 -s 0
n -t * -a 0 -s 0 -S UP -v circle -c black -i black
n -t * -a 1 -s 1 -S UP -v circle -c black -i black
l -t * -s 0 -d 1 -S UP -r 1000000 -D 0.01 -c black
+ -t 0.5 -s 0 -d 1 -p cbr -e 1000 -c 0 -i 0 -a 0 -x {0.0 1.0 0 ------- null}
- -t 0.5 -s 0 -d 1 -p cbr -e 1000 -c 0 -i 0 -a 0 -x {0.0 1.0 0 ------- null}
h -t 0.5 -s 0 -d 1 -p cbr -e 1000 -c 0 -i 0 -a 0 -x {0.0 1.0 -1 ------- null}
+ -t 0.506666666666667 -s 0 -d 1 -p cbr -e 1000 -c 0 -i 1 -a 0 -x {0.0 1.0 1 ------- null}
- -t 0.508 -s 0 -d 1 -p cbr -e 1000 -c 0 -i 1 -a 0 -x {0.0 1.0 1 ------- null}
h -t 0.508 -s 0 -d 1 -p cbr -e 1000 -c 0 -i 1 -a 0 -x {0.0 1.0 -1 ------- null}
RecordMeaning (ns Manual, section 49.1)
Vthe version of NAM the file needs
Ahow addresses are laid out
na node: -s its id, -S UP its state, -v its shape, -c its colour
la link: -s and -d its ends, -r its bandwidth, -D its delay
+, -a packet entered or left a queue
ha hop: the packet started along the link
rthe packet finished arriving at the far end
da drop

The link line carries -r 1000000 -D 0.01: bits per second and seconds, for the script's 1Mb 10ms. (The manual's text says megabits and milliseconds, but its own example line, like every file ns-2.35 writes, uses bits per second and seconds.) Every record with -t * is set-up, read before the animation starts; every other one happens at its time.

The wireless NAM file adds positions and the size of the area:

$ head -5 wireless.nam
n -t * -s 0  -x 50 -y 100 -Z 0 -z 30  -v circle -c black
n -t * -s 1  -x 250 -y 100 -Z 0 -z 30  -v circle -c black
n -t * -s 2  -x 450 -y 100 -Z 0 -z 30  -v circle -c black
V -t * -v 1.0a5 -a 0
W -t * -x 500 -y 200
munotes.in116

The Network Simulator Laboratory from Zero: NS-2, Tcl, the Trace File and NAM

-x and -y are the node's position in metres, -z 30 the size NAM draws it at (the 30 of initial_node_pos), and W gives NAM the width and height of the area, the 500 and 200 of load_flatgrid.

Opening NAM on Ubuntu 22.04

To watch a run, open its NAM file:

$ nam wireless.nam
nam:

On Ubuntu 22.04 that is all that happens: nam: and nothing after it, and no window. It happens on every computer, not just yours; Debian has the same package reported broken since March 2022 (bug #1006986). The cause is a clash between two parts NAM is built on. Before it starts its window system, Tk, NAM loads a library called tclcl, and tclcl replaces Tcl's source command with its own version, which accepts one argument, a file name (tcl-import.tcl in tclcl's source: proc source {fileName}). Tk 8.6.12, the version in Ubuntu 22.04, loads its own files with source -encoding utf-8 file: three arguments. tclcl's version refuses them, Tk fails to start, and NAM exits. The message is empty because NAM prints the error from a place Tcl 8.6 no longer fills; a copy of NAM rebuilt to print it properly says wrong # args: should be "source fileName".

The fix needs no compiling: give NAM its own copy of Tk's library files with -encoding utf-8 removed, and point NAM at the copy with the variable TK_LIBRARY. Three commands, once:

$ cp -r /usr/share/tcltk/tk8.6 ~/tk8.6-nam
$ grep -rl -- "source -encoding utf-8" ~/tk8.6-nam | xargs sed -i 's/source -encoding utf-8 /source /g'
$ grep -rl -- "source -encoding" ~/tk8.6-nam | wc -l
0

The last command counts the files that still carry the option: none. Then make an alias, so that plain nam always starts with the copy:

$ echo "alias nam='TK_LIBRARY=\$HOME/tk8.6-nam nam'" >> ~/.bashrc
$ source ~/.bashrc
$ nam wireless.nam

Two windows open: a console with NAM's welcome text, which can be closed, and the animation window, with the nodes laid out and a row of buttons to play, stop and rewind. Practical 11 shows the window and its controls. Many scripts start NAM themselves, with exec nam wireless.nam & in finish before exit 0; the alias does not reach that, because aliases belong to your shell, not to programs a script starts. This book's scripts leave it out: run NAM yourself, after ns has finished.

The shape of every Module 2 practical

Every simulation in Module 2 is the skeleton of step 5 with three things changed: the parts in node-config, the nodes and how they move, and the traffic. The run and its measurement are always the same two commands:

munotes.in117

The Network Simulator Laboratory from Zero: NS-2, Tcl, the Trace File and NAM

$ ns practical.tcl
$ awk -f measure.awk practical.tr
Part of the scriptWhat changes from practical to practical
set val(...)the routing protocol, the MAC, the number of nodes, the area, the stop time
node-configthe parts every node is built from
the nodesfixed positions, or a movement file made by setdest (Practical 11)
the trafficone CBR flow, or many made by cbrgen.tcl
finishalways the same: flush, close, exit

Where a practical asks for something NS-2 as it ships does not have, its chapter says so and does it another way. The first example is Practical 18: the only antenna class in NS-2 2.35 is Antenna/OmniAntenna, which radiates equally in every direction, so a directional antenna has to be modelled outside it.

Procedure

  1. Install ns2, nam and tcl8.6 and check that ns runs a script.
  2. Run a Tcl script to practise variables, [ ], expr, procedures, loops and arrays.
  3. Write and run a two-node wired simulation with a UDP flow, recording a trace and a NAM file.
  4. Decode the wired trace's twelve fields and count its events by type.
  5. Write and run the wireless skeleton: three mobile nodes in a line, running AODV.
  6. Work out the receive and carrier-sense ranges from the radio's thresholds.
  7. Decode the wireless trace's fields, and read the route discovery in it.
  8. Measure both runs with awk: packets offered, dropped and received, throughput, delivery ratio, delay and routing packets.
  9. Read the NAM file's records, and open it in nam where there is a screen.

Observations

MeasuredValue
NS-2 and NAMns2 2.35, nam 1.15, from Ubuntu 22.04
Wired run: packets offered, sent, dropped, received300, 259, 41, 259
First drop0.866667 s, 0.366667 s after the source started
Packets sent after the source stopped9, the queue's nine places
Throughput from 1 to 2 s1000.0 kbit/s, the link's capacity
Receive and carrier-sense range250.0 m and 550.0 m
Wireless run: data packets sent and received16 and 16
Packet delivery ratio100.0 per cent
Average end-to-end delay12.55 ms; the first packet, which waited for a route, 24.08 ms
Routing packets4: request, rebroadcast request, reply, forwarded reply
Data packet size at the agent and on the air512 bytes and 532 bytes

Result

NS-2 2.35 and NAM 1.15 were installed on Ubuntu 22.04 and ran a wired and a wireless simulation from Tcl scripts. On the wired link of 1 Mb/s, a source offering 1.2 Mb/s saw 41 of its 300 packets dropped, the first when the drop-tail queue's nine places filled, and the link delivered exactly 1000 kbit/s while it was full. In the wireless line of three nodes 200 m apart, where the default radio receives within 250 m and senses within 550 m, AODV found the route through the middle node with four routing packets and delivered all 16 data packets, with an average end-to-end delay of 12.55 ms. The trace and NAM files were decoded field by field, and every measurement was counted from the trace with awk.

munotes.in118

The Network Simulator Laboratory from Zero: NS-2, Tcl, the Trace File and NAM

Where marks are lost

Forgetting $ns run. Nothing in a script happens before it. Without it ns exits at once and both files are empty.

Leaving out create-god. The first $ns node of a wireless script then stops the run with Cannot find instance of god, and the trace is empty.

Reading a wireless trace with the wired fields. In a wireless trace the node is $3, written _2_, the layer is $4 and the type is $7; in a wired trace the type is $5. An awk script written for one reads nonsense from the other.

Counting every received data line as a delivery. A packet is logged r at every routing agent it passes. A delivery is an r at the AGT layer of the destination, and a delivery ratio above 100 per cent means the pattern forgot $4 == "AGT".

Measuring throughput from the first packet to the last. That window includes the start and the end of the flow. Measure over a steady window and say which one.

Expecting NAM to work as installed. On Ubuntu 22.04 it prints nam: and stops. Apply the fix in step 8 once, and write it into your journal as part of the setup.

Quoting the ns Manual's example for the MAC field. Its a2 is hexadecimal, 162, and in microseconds, not "160+2 sec".

Writing -channelType from an old script. It still works on 2.35 but prints warning: Please use -channel as shown in tcl/ex/wireless-mitf.tcl at every run; write -channel [new $val(chan)].

Putting nodes 400 m apart and expecting them to talk. With the defaults they cannot: the receive range is 250 m.

For the journal

This chapter is the laboratory setup for Module 2. If your college asks for it as a practical, write: aim; what NS-2, OTcl and NAM are, in three lines each; the installation; the Tcl commands a script uses; wired.tcl, the table of its lines, and the first lines of its trace decoded field by field; the event counts and why the queue dropped 41; wireless.tcl with its node-config table; the receive and carrier-sense ranges; the wireless trace's fields; both awk scripts with their output; the NAM records; the observation table; the result.

munotes.in119

The Network Simulator Laboratory from Zero: NS-2, Tcl, the Trace File and NAM

Quick revision

  • NS-2: protocols in C++, scripts in OTcl; ns script.tcl runs one.
  • A script: new Simulator, trace files, nodes, links or node-config, agents, traffic, $ns at events, finish, $ns run.
  • trace-all writes the trace, namtrace-all (or namtrace-all-wireless) the NAM file.
  • Wired trace: event, time, from, to, type, size, flags, flow, source, destination, sequence, id; events + - r d.
  • A CBR source sends every packetSize_ × 8 / rate_ seconds.
  • DropTail with a limit of 10 lets nine packets wait.
  • node-config builds a mobile node: routing, LL, MAC, queue, antenna, propagation, PHY, channel, topography.
  • Default radio: receives within 250 m, senses within 550 m.
  • Wireless trace: event (s r f D), time, _node_, layer (AGT RTR MAC IFQ), reason, id, type, size, [MAC], [IP], type-specific fields.
  • Delivery ratio = received at the destination's AGT ÷ sent by the source's AGT.
  • NAM records: V A n l W for set-up, + - h r d for packets.
  • NAM on Ubuntu 22.04 starts only with TK_LIBRARY naming a copy of Tk's library without -encoding utf-8.

Questions you must be able to answer

1. What is the difference between NS-2 and TOSSIM? Both are discrete-event simulators. TOSSIM runs your own TinyOS program on simulated motes. NS-2 runs its own implementations of standard protocols, such as 802.11 and AODV, on a network you describe in a Tcl script.

2. Why is an NS-2 script written in Tcl when NS-2 is written in C++? The protocols are in C++ because they run millions of times and must be fast. The network and the experiment are in OTcl because they change with every run, and a script can be edited and rerun without recompiling the simulator.

3. What are the three outputs of a run, and what reads each? The trace file, read with awk to measure the network; the NAM file, read by the animator to show it; and anything the script prints with puts, on the terminal.

4. What do the four event symbols of a wired trace mean? + the packet entered a link's queue, - it left the queue and started along the link, r it arrived at the far end, d it was dropped.

5. A 1 Mb/s link with a queue limit of 10 is offered 150 packets of 1000 bytes a second. How many are dropped each second once the queue is full, and why is the limit not the number that wait? The link carries 125 a second, so 150 - 125 = 25 a second are dropped. NS-2's drop-tail queue drops an arrival when its length plus one would reach the limit, so with a limit of 10 at most nine packets wait; the packet being sent is on the link.

munotes.in120

The Network Simulator Laboratory from Zero: NS-2, Tcl, the Trace File and NAM

6. What does node-config do, and why is it needed only for wireless nodes? It lists the parts every mobile node created after it is built from: routing protocol, link layer, MAC, interface queue, antenna, propagation model, radio and channel. A wired node needs none of them; its links are made separately with duplex-link.

7. Why do two nodes 400 m apart not communicate with NS-2's default radio? Because the default receive threshold, with the two-ray ground model, corresponds to 250 m. At 400 m the signal is above the carrier-sense threshold, which corresponds to 550 m, so each can tell the other is transmitting, but neither can decode the other's packets.

8. Decode r 1.017795713 _1_ RTR --- 0 cbr 532 [13a 1 0 800] ------- [0:0 2:0 30 1] [0] 1 0. Node 1's routing layer received packet 0, a 532-byte CBR packet, at 1.017795713 s, with no drop reason. It came from MAC address 0 to MAC address 1, as IP (0x800), reserving the medium for 0x13a, 314 microseconds, for its acknowledgement. It is from node 0, port 0, to node 2, port 0, with TTL 30 and next hop 1. It is CBR packet number 0 and has been forwarded once.

9. Why is the data packet 512 bytes at the agent and 532 bytes on the air? AODV adds the 20 bytes of the IP header to a packet that its own node originates.

10. How is the packet delivery ratio calculated from a wireless trace? Count the data packets sent at the source's agent layer, s and AGT, and those received at the destination's agent layer, r and AGT; the ratio is the second over the first, as a percentage.

11. Why must the awk pattern for deliveries name the AGT layer? Because a data packet is also logged as received at the routing layer of every node it passes, including the source's own. Without the layer, each packet is counted once for every hop, and the ratio can exceed 100 per cent.

12. What is in the first lines of a NAM file, and what does -t mean? The version, the address layout, and a record for every node and link, or for a wireless run the nodes' positions and the size of the area. -t marks a set-up record, which NAM reads before the animation starts.

Contents This chapter on its own page

munotes.in121

Chapter Fourteen

Practical 11: A Basic MANET with Packet Animation

Syllabus topic Module 2, "Basic MANET Implementation with Packet Animation: Simulate a mobile ad-hoc network and visualize packet movement to study dynamic topology formation."

Aim

To simulate a mobile ad hoc network in NS-2 whose nodes move, to animate it in NAM, and to follow from the trace how its topology, and the route across it, forms, breaks and forms again.

What you need to know before you start

A mobile ad hoc network (MANET) is a network of wireless nodes with no base station and no cables. Every node is a host and a router: it sends its own packets and forwards other nodes' packets towards their destinations. And the nodes move, so which node can hear which, the network's topology, changes all the time, and a route that worked a second ago can stop working. A MANET's routing protocol exists to cope with that. This practical makes the topology change on purpose and watches what happens to the packets.

A link, in a simulated MANET, is a matter of distance. Chapter 13 worked out that with NS-2's default radio a node receives what is sent within 250 m. So two nodes are neighbours exactly when they are within 250 m of each other, and the topology at any moment can be read off the nodes' positions.

Moving a node in NS-2 is one command. $node setdest x y speed makes the node start travelling, in a straight line at a constant speed in metres per second, towards the point (x, y), and stop when it gets there. Scheduled with $ns at, it happens at a chosen time:

$ns at 10.0 "$node(2) setdest 450 40 20"

For many nodes, the movement is generated. The usual model is the random waypoint model: each node picks a random point in the area and a random speed, travels there, pauses, and picks again. NS-2 ships a generator for it, also called setdest, which writes the $ns at ... setdest commands for every node into a file that a script reads with source. Step 7 uses it.

Packet animation is NAM's job: it replays the NAM file, moving the nodes as they moved and showing every packet as it is sent from node to node. On Ubuntu 22.04 NAM needs one fix before it will start at all; step 3 gives it.

Step 1: a MANET that changes on purpose

Six nodes in an area 800 m by 600 m. Node 0 sends a 512-byte packet every quarter of a second to node 5, 600 m away, which is too far for one hop or two, so every packet needs at least two relays. Four nodes can relay: 1 and 2 start in a line between 0 and 5; 3 and 4 start 260 m off the line, at y = 560, out of everyone's range but each other's. Then four setdest commands change the topology, as the table says. manet.tcl:

munotes.in122

Practical 11: A Basic MANET with Packet Animation

# manet.tcl: six mobile nodes, AODV, one flow from node 0 to node 5, and nodes that move
set val(chan)   Channel/WirelessChannel
set val(prop)   Propagation/TwoRayGround
set val(netif)  Phy/WirelessPhy
set val(mac)    Mac/802_11
set val(ifq)    Queue/DropTail/PriQueue
set val(ll)     LL
set val(ant)    Antenna/OmniAntenna
set val(ifqlen) 50
set val(nn)     6
set val(rp)     AODV
set val(x)      800
set val(y)      600
set val(stop)   45.0

set ns [new Simulator]
set tf [open manet.tr w]
$ns trace-all $tf
set nf [open manet.nam w]
$ns namtrace-all-wireless $nf $val(x) $val(y)

set topo [new Topography]
$topo load_flatgrid $val(x) $val(y)
create-god $val(nn)

$ns node-config -adhocRouting $val(rp) -llType $val(ll) -macType $val(mac) \
    -ifqType $val(ifq) -ifqLen $val(ifqlen) -antType $val(ant) \
    -propType $val(prop) -phyType $val(netif) -channel [new $val(chan)] \
    -topoInstance $topo -agentTrace ON -routerTrace ON -macTrace OFF \
    -movementTrace ON

# where each node starts: x y
set start {
    {50 300}
    {250 300}
    {450 300}
    {250 560}
    {450 560}
    {650 300}
}
for {set i 0} {$i < $val(nn)} {incr i} {
    set node($i) [$ns node]
    $node($i) random-motion 0
    $node($i) set X_ [lindex $start $i 0]
    $node($i) set Y_ [lindex $start $i 1]
    $node($i) set Z_ 0
    $ns initial_node_pos $node($i) 30
}

# how the topology changes: at time t, node n heads for (x, y) at speed m/s
$ns at 10.0 "$node(2) setdest 450 40 20"
$ns at 20.0 "$node(4) setdest 450 300 20"
$ns at 30.0 "$node(1) setdest 250 40 20"
$ns at 30.0 "$node(3) setdest 250 300 20"

set udp [new Agent/UDP]
$ns attach-agent $node(0) $udp
set sink [new Agent/Null]
$ns attach-agent $node(5) $sink
$ns connect $udp $sink
set cbr [new Application/Traffic/CBR]
$cbr set packetSize_ 512
$cbr set interval_ 0.25
$cbr attach-agent $udp
$ns at 1.0 "$cbr start"
$ns at 44.0 "$cbr stop"

for {set i 0} {$i < $val(nn)} {incr i} {
    $ns at $val(stop) "$node($i) reset"
}
$ns at $val(stop) "finish"
proc finish {} {
    global ns tf nf
    $ns flush-trace
    close $tf
    close $nf
    exit 0
}
$ns run

The script is Chapter 13's skeleton with three changes: six nodes, their start positions in a Tcl list read with lindex, and -movementTrace ON, which writes every movement into the trace. The four moves were chosen so the geometry can be worked out before the run:

TimeMoveWhat it should do to the topology
10 snode 2 heads for (450, 40) at 20 m/snode 2 leaves the line; 150 m from it, at 17.5 s, it is 250 m from nodes 1 and 5, and both links break
17.5 to 25.5 snothing newno path from 0 to 5 at all: the network is partitioned
20 snode 4 heads for (450, 300) at 20 m/s110 m along its way, at 25.5 s, node 4 is 250 m from nodes 1 and 5: the path 0-1-4-5 exists
30 snode 1 heads for (250, 40), node 3 for (250, 300), both at 20 m/sat 35.5 s node 3 comes within 250 m of node 0, and at 37.5 s node 1 leaves it: the path moves to 0-3-4-5
munotes.in123

Practical 11: A Basic MANET with Packet Animation

Each time comes from Pythagoras. Two nodes whose x coordinates differ by 200 m are exactly 250 m apart when their y coordinates differ by 150 m, because 200² + 150² = 62500 = 250². So node 2, moving at 20 m/s, breaks its links after 150 / 20 = 7.5 s, at 10 + 7.5 = 17.5 s, and node 4 makes its links after (260 - 150) / 20 = 5.5 s, at 20 + 5.5 = 25.5 s.

Step 2: run it, and find the movement in both files

$ ns manet.tcl
num_nodes is set 6
INITIALIZE THE LIST xListHead
channel.cc:sendUp - Calc highestAntennaZ_ and distCST_
highestAntennaZ_ = 1.5,  distCST_ = 550.0
SORTING LISTS ...DONE!
$ grep "^M" manet.tr
M 10.00000 2 (450.00, 300.00, 0.00), (450.00, 40.00), 20.00
M 20.00000 4 (450.00, 560.00, 0.00), (450.00, 300.00), 20.00
M 30.00000 1 (250.00, 300.00, 0.00), (250.00, 40.00), 20.00
M 30.00000 3 (250.00, 560.00, 0.00), (250.00, 300.00), 20.00

A mobile node's movements appear in the trace as M lines, one per setdest, written by MobileNode::log_movement(): the time, the node, where it is now (x, y, z), where it is going (x, y), and its speed. The NAM file records the same four moves differently:

$ grep "^n -t [0-9]" manet.nam
n -t 10.000000 -s 2 -x 450.000000 -y 300.000000 -U 0.000000 -V -20.000000 -T 13.000000
n -t 20.000000 -s 4 -x 450.000000 -y 560.000000 -U 0.000000 -V -20.000000 -T 13.000000
n -t 30.000000 -s 1 -x 250.000000 -y 300.000000 -U 0.000000 -V -20.000000 -T 13.000000
n -t 30.000000 -s 3 -x 250.000000 -y 560.000000 -U 0.000000 -V -20.000000 -T 13.000000

A NAM node record with a real time, not *, is a change: from -t the node at -x, -y moves with velocity -U metres per second along x and -V along y, for -T seconds. Node 2's record says it moves at -20 m/s in y for 13 s: 300 - 13 × 20 = 40, the destination. NAM's -V is negative because node 2 is heading for a smaller y.

Step 3: animating it in NAM, on Ubuntu 22.04

NAM does not start on Ubuntu 22.04 as installed: it prints nam: and stops. Chapter 13's step 8 explains why and fixes it. If you have not applied that fix on this computer, do it now, with the same commands:

munotes.in124

Practical 11: A Basic MANET with Packet Animation

$ cp -r /usr/share/tcltk/tk8.6 ~/tk8.6-nam
$ grep -rl -- "source -encoding utf-8" ~/tk8.6-nam | xargs sed -i 's/source -encoding utf-8 /source /g'
$ grep -rl -- "source -encoding" ~/tk8.6-nam | wc -l
0

Then add the alias, if it is not already in your ~/.bashrc from Chapter 13, and open the NAM file:

$ echo "alias nam='TK_LIBRARY=\$HOME/tk8.6-nam nam'" >> ~/.bashrc
$ source ~/.bashrc
$ nam manet.nam

Two windows open: a console with NAM's welcome text, which can be closed, and the animation window, headed with the file's name:

NAM's animation window for manet.nam, stopped at 36 seconds. Along the top: the File, Views and Analysis menus; five buttons for rewind, play backwards, stop, play and fast forward; the time, 36.000000; and the time step, 2.0 ms. The six nodes are drawn as numbered circles in the middle: 0, 4 and 5 on a line, 1 below and left, 3 above, 2 at the bottom. Under the picture is the time slider.

Figure 14.1 NAM playing manet.nam, stopped at 36 s: a real screenshot

ControlWhat it does (NAM's own help)
rewind, fast forwardmove the animation back or forward by 25 time steps
play backwards, stop, playrun the animation in either direction, or halt it
the time readoutthe simulated time now on screen
Stephow much simulated time passes per frame of animation; drag the slider to change it
the slider under the picturejump to any time
clicking a node while playingshows information about it
the zoom buttons at the leftzoom in and out

Press play. The nodes stand still until 10 s, then node 2 slides downwards, and packets appear as small marks moving from node to node along the chain. Three things to notice on screen. NAM draws y upwards: nodes 3 and 4, at y = 560, are at the top, and node 2, heading for y = 40, goes down. NAM draws no links: in a wireless network there are no wires to draw, and which nodes are neighbours has to be worked out from the distances. And packets are brief: a 532-byte packet takes about 6 ms on each hop, so at the default step most frames show none in flight.

Step 4: the frames, with the links NAM does not draw

NAM shows positions but not links or routes, and the purpose of this practical is to watch topology form. frames.py works them out. It reads the NAM file to place every node at any moment, joins every pair within 250 m, and reads the trace to find which way the latest data packet actually went:

# frames.py: where every node is, which links exist, and the route in use, at chosen times
import math
import sys

RANGE = 250.0      # metres: NS-2's default receive range (the NS-2 laboratory chapter)
SOURCE = '_0_'     # the node the data flow starts from


def read_nam(path):
    """Start positions, and every movement record, from a wireless NAM file."""
    start, moves = {}, {}
    for line in open(path):
        if not line.startswith('n -t '):
            continue
        f = line.split()
        o = dict(zip(f[1::2], f[2::2]))
        n = int(o['-s'])
        if o['-t'] == '*':
            start[n] = (float(o['-x']), float(o['-y']))
        else:
            moves.setdefault(n, []).append((float(o['-t']), float(o['-x']), float(o['-y']),
                                            float(o['-U']), float(o['-V']), float(o['-T'])))
    return start, moves


def position(start, moves, n, t):
    x, y = start[n]
    for t0, x0, y0, u, v, dur in moves.get(n, []):
        if t0 > t:
            break
        dt = min(t - t0, dur)
        x, y = x0 + u * dt, y0 + v * dt
    return x, y


def links(pos):
    ids = sorted(pos)
    return [(a, b) for i, a in enumerate(ids) for b in ids[i + 1:]
            if math.dist(pos[a], pos[b]) <= RANGE]


def read_trace(path):
    """Every data packet: when the source sent it, who forwarded it, how it ended."""
    sent, hops, fate = [], {}, {}
    for line in open(path):
        f = line.split()
        if len(f) < 7 or f[6] != 'cbr':
            continue
        if f[0] == 's' and f[3] == 'AGT' and f[2] == SOURCE:
            sent.append((float(f[1]), f[5]))
            hops[f[5]] = [SOURCE.strip('_')]
        elif f[0] == 'f' and f[3] == 'RTR':
            hops[f[5]].append(f[2].strip('_'))
        elif f[0] == 'r' and f[3] == 'AGT':
            hops[f[5]].append(f[2].strip('_'))
            fate[f[5]] = 'delivered'
        elif f[0] == 'D':
            fate[f[5]] = {'NRTE': 'lost, no route', 'CBK': 'lost, link broke'}.get(f[4], 'lost, ' + f[4])
    return sent, hops, fate


def frame(t, start, moves, sent, hops, fate):
    pos = {n: position(start, moves, n, t) for n in start}
    print('at %.3f s' % t)
    print('  nodes ' + '  '.join('%d (%g, %g)' % (n, round(x), round(y)) for n, (x, y) in sorted(pos.items())))
    print('  links ' + '  '.join('%d-%d' % l for l in links(pos)))
    last = [(ts, uid) for ts, uid in sent if ts <= t]
    if last:
        ts, uid = last[-1]
        print('  route packet %s, sent at %.3f s: %s, %s' % (uid, ts, '-'.join(hops[uid]),
                                                           fate.get(uid, 'still on its way')))


def timeline(start, moves, stop):
    before = set()
    for k in range(int(stop * 10) + 1):
        t = k / 10
        now = set(links({n: position(start, moves, n, t) for n in start}))
        up, down = sorted(now - before), sorted(before - now)
        if up:
            print('%5.1f s  up    %s' % (t, '  '.join('%d-%d' % l for l in up)))
        if down:
            print('%5.1f s  down  %s' % (t, '  '.join('%d-%d' % l for l in down)))
        before = now


if __name__ == '__main__':
    start, moves = read_nam(sys.argv[1])
    if sys.argv[2] == '--links':
        timeline(start, moves, float(sys.argv[3]))
    else:
        sent, hops, fate = read_trace(sys.argv[2])
        for t in sys.argv[3:]:
            frame(float(t), start, moves, sent, hops, fate)
munotes.in125

Practical 11: A Basic MANET with Packet Animation

A node's position at time t is its last movement record's starting point plus its velocity times the time since, stopping when the record's duration runs out. A packet's route is the source, then every node that logged an f for it, then the destination's agent. Four frames, one in each chapter of the story:

munotes.in126

Practical 11: A Basic MANET with Packet Animation

$ python3 frames.py manet.nam manet.tr 5 21 36 42
at 5.000 s
  nodes 0 (50, 300)  1 (250, 300)  2 (450, 300)  3 (250, 560)  4 (450, 560)  5 (650, 300)
  links 0-1  1-2  2-5  3-4
  route packet 16, sent at 5.000 s: 0-1-2-5, delivered
at 21.000 s
  nodes 0 (50, 300)  1 (250, 300)  2 (450, 80)  3 (250, 560)  4 (450, 540)  5 (650, 300)
  links 0-1  3-4
  route packet 80, sent at 21.000 s: 0, lost, no route
at 36.000 s
  nodes 0 (50, 300)  1 (250, 180)  2 (450, 40)  3 (250, 440)  4 (450, 300)  5 (650, 300)
  links 0-1  0-3  1-2  1-4  3-4  4-5
  route packet 140, sent at 36.000 s: 0-1-4-5, delivered
at 42.000 s
  nodes 0 (50, 300)  1 (250, 60)  2 (450, 40)  3 (250, 320)  4 (450, 300)  5 (650, 300)
  links 0-3  1-2  3-4  4-5
  route packet 164, sent at 42.000 s: 0-3-4-5, delivered
Four frames of the MANET, at 5, 21, 36 and 42 seconds, each showing the six nodes, thin lines for every pair within 250 metres, and a heavy arrowed line for the route the latest data packet took. At 5 s the route is 0-1-2-5. At 21 s node 2 is far below, nodes 3 and 4 are linked only to each other, and there is no route. At 36 s node 4 has moved into the gap and the route is 0-1-4-5. At 42 s node 1 has gone down and node 3 has come in, and the route is 0-3-4-5.

Figure 14.2 The topology and the route in use, at 5, 21, 36 and 42 s, drawn from frames.py

Step 5: the topology as it forms and breaks

With --links, frames.py steps through the run a tenth of a second at a time and prints every link that appears or disappears:

$ python3 frames.py manet.nam --links 45
  0.0 s  up    0-1  1-2  2-5  3-4
 17.6 s  down  1-2  2-5
 25.5 s  up    1-4  4-5
 27.6 s  down  3-4
 35.5 s  up    0-3  1-2  3-4
 37.6 s  down  0-1  1-4

Compare this with the predictions of step 1: every one is there. Each printed time is the first tenth of a second at which the link has gone or come, so the link from 1 to 2, exactly 250 m long at 17.5 s, is listed as gone at 17.6 s. Four changes were not in the table, and follow from the same geometry: node 4, heading for the line, leaves node 3 behind at 27.5 s; node 3, following it, meets node 4 again at 35.5 s; node 1, heading away from the line, comes within 250 m of node 2 at 35.5 s; and it leaves node 4 at 37.5 s, when it leaves node 0. This is dynamic topology formation: nothing in the network was reconfigured, the nodes only moved, and ten links came or went at five moments in 45 seconds.

Step 6: what happened to the packets

The routing protocol, AODV, has to follow those changes. The trace says how well it did. route.awk prints one line per group of consecutive data packets that went the same way, or were lost the same way:

# route.awk: the route every data packet took, grouped into runs of packets that went the same way
$7 == "cbr" && $1 == "s" && $4 == "AGT" { t[$6] = $2; path[$6] = "0"; ids[++n] = $6 }
$7 == "cbr" && $1 == "f" && $4 == "RTR" { path[$6] = path[$6] "-" substr($3, 2, length($3) - 2) }
$7 == "cbr" && $1 == "r" && $4 == "AGT" { got[$6] = 1; path[$6] = path[$6] "-5" }
$7 == "cbr" && $1 == "D" { why[$6] = $5 }
END {
    for (i = 1; i <= n; i++) {
        id = ids[i]
        how = (id in got) ? "delivered via " path[id] : "LOST (" why[id] ") at node " substr(path[id], length(path[id]))
        if (how != last) {
            if (i > 1) printf "%6.2f to %6.2f s  %3d packets  %s\n", first, t[ids[i - 1]], count, last
            first = t[id]; count = 0; last = how
        }
        count++
    }
    printf "%6.2f to %6.2f s  %3d packets  %s\n", first, t[ids[n]], count, last
}
munotes.in127

Practical 11: A Basic MANET with Packet Animation

$ awk -f route.awk manet.tr
  1.00 to  17.25 s   66 packets  delivered via 0-1-2-5
 17.50 to  17.50 s    1 packets  LOST (CBK) at node 1
 17.75 to  24.25 s   27 packets  LOST (NRTE) at node 0
 24.50 to  37.25 s   52 packets  delivered via 0-1-4-5
 37.50 to  37.50 s    1 packets  LOST (CBK) at node 0
 37.75 to  43.75 s   25 packets  delivered via 0-3-4-5

Read it against the link timeline.

The first route lasted until the link broke. At 17.5 s node 1 sent a packet to node 2, which was no longer there. The MAC tried, failed, and reported the failure to AODV at node 1 (CBK, the MAC callback); the packet was lost, and node 1 sent a route ERROR back to node 0.

During the partition, every packet was lost at the source. Node 0 searched for a new route and there was none. When its search gave up, it dropped every packet it had been holding for node 5 (NRTE, no route).

Packets sent after that waited, then went the new way. The path through node 4 existed from 25.5 s, but the first packet to arrive after the partition arrived much later:

$ awk '$1 == "r" && $4 == "AGT" && $7 == "cbr" && $2 > 17.5' manet.tr | head -1
r 34.291265576 _5_ AGT  --- 94 cbr 532 [13a 5 4 800] ------- [0:0 5:0 28 5] [94] 3 0

Packet 94, sent at 24.50 s, reached node 5 at 34.29 s, from node 4, after three hops and with its TTL down from 30 to 28: nearly ten seconds on a journey that takes 18 ms when a route is ready. Packets sent from 24.50 s on were held at node 0 until AODV found the new route, then delivered: late, but delivered. Practical 12 measures why the search waited that long.

munotes.in128

Practical 11: A Basic MANET with Packet Animation

The third route replaced the second only when the second broke. Node 3 came within range of node 0 at 35.5 s, but packets kept going through node 1 until the link from 0 to 1 broke at 37.5 s. Then one more packet was lost, and the route moved to 0-3-4-5 within the next quarter of a second. AODV does not look for better routes; it repairs broken ones.

Totalled over the run:

$ awk '$1 == "s" && $4 == "AGT" && $7 == "cbr"' manet.tr | wc -l
172
$ awk '$1 == "r" && $4 == "AGT" && $7 == "cbr"' manet.tr | wc -l
143

143 of 172 delivered: a packet delivery ratio of 83.1 per cent, every lost packet accounted for above: 1 + 27 + 1 = 29, and 172 - 143 = 29.

Step 7: random movement with the setdest generator

Scripted moves make a story that can be predicted and checked. Real MANET experiments use many nodes moving at random, generated by setdest. It takes the version (1 is the original random waypoint model), the number of nodes, the pause at each waypoint, the maximum speed, the length of the run and the size of the area:

$ setdest -v 1 -n 10 -p 2 -M 10 -t 50 -x 500 -y 500 > scen-random
$ wc -l scen-random
156 scen-random
$ head -9 scen-random
#
# nodes: 10, pause: 2.00, max speed: 10.00, max x: 500.00, max y: 500.00
#
$node_(0) set X_ 480.194053934040
$node_(0) set Y_ 147.303430978002
$node_(0) set Z_ 0.000000000000
$node_(1) set X_ 225.688158521228
$node_(1) set Y_ 168.327044931246
$node_(1) set Z_ 0.000000000000

The file is Tcl, written for a script that names its simulator ns_, its nodes node_(0) to node_(9) and its God god_. It holds, in order, each node's starting position; $god_ set-dist lines telling God how many hops separate every pair of nodes at the start; and $ns_ at lines with every move, each followed by the set-dist changes it causes:

$ grep -c "setdest" scen-random
18
$ grep -c "set-dist" scen-random
86

Your file will not be this one. setdest seeds its random numbers from the clock, so every run of it writes a different file, and your numbers will differ from these. Keep the file you used with your journal: it is your experiment's input.

To use it, the script must use those names and source the file after creating the nodes. random.tcl:

# random.tcl: ten nodes moving by a setdest file, AODV, two CBR flows
set val(chan)   Channel/WirelessChannel
set val(prop)   Propagation/TwoRayGround
set val(netif)  Phy/WirelessPhy
set val(mac)    Mac/802_11
set val(ifq)    Queue/DropTail/PriQueue
set val(ll)     LL
set val(ant)    Antenna/OmniAntenna
set val(ifqlen) 50
set val(nn)     10
set val(rp)     AODV
set val(x)      500
set val(y)      500
set val(stop)   50.0

set ns_ [new Simulator]
set tf [open random.tr w]
$ns_ trace-all $tf
set nf [open random.nam w]
$ns_ namtrace-all-wireless $nf $val(x) $val(y)
set topo [new Topography]
$topo load_flatgrid $val(x) $val(y)
set god_ [create-god $val(nn)]

$ns_ node-config -adhocRouting $val(rp) -llType $val(ll) -macType $val(mac) \
    -ifqType $val(ifq) -ifqLen $val(ifqlen) -antType $val(ant) \
    -propType $val(prop) -phyType $val(netif) -channel [new $val(chan)] \
    -topoInstance $topo -agentTrace ON -routerTrace ON -macTrace OFF \
    -movementTrace OFF
for {set i 0} {$i < $val(nn)} {incr i} {
    set node_($i) [$ns_ node]
    $node_($i) random-motion 0
}
source scen-random
for {set i 0} {$i < $val(nn)} {incr i} {
    $ns_ initial_node_pos $node_($i) 30
}

# two flows: 0 to 9 and 3 to 6, a 512-byte packet every half second each
foreach {src dst} {0 9 3 6} {
    set udp [new Agent/UDP]
    $ns_ attach-agent $node_($src) $udp
    set sink [new Agent/Null]
    $ns_ attach-agent $node_($dst) $sink
    $ns_ connect $udp $sink
    set cbr [new Application/Traffic/CBR]
    $cbr set packetSize_ 512
    $cbr set interval_ 0.5
    $cbr attach-agent $udp
    $ns_ at 2.0 "$cbr start"
    $ns_ at 48.0 "$cbr stop"
}
for {set i 0} {$i < $val(nn)} {incr i} {
    $ns_ at $val(stop) "$node_($i) reset"
}
$ns_ at $val(stop) "finish"
proc finish {} {
    global ns_ tf nf
    $ns_ flush-trace
    close $tf
    close $nf
    exit 0
}
$ns_ run
munotes.in129

Practical 11: A Basic MANET with Packet Animation

initial_node_pos comes after source, because NAM draws each node where it stands when that line runs. Run it and count the deliveries:

$ ns random.tcl > /dev/null
INITIALIZE THE LIST xListHead
SORTING LISTS ...DONE!
$ awk '$1 == "s" && $4 == "AGT" && $7 == "cbr"' random.tr | wc -l
184
$ awk '$1 == "r" && $4 == "AGT" && $7 == "cbr"' random.tr | wc -l
183

The two lines left on the screen are the channel's, which go to the error stream and so pass the > /dev/null; Chapter 13 explains them.

Whatever your numbers, frames.py can show you your own run: its SOURCE line names the source of the flow it follows, and --links works on any wireless NAM file.

Procedure

  1. Write manet.tcl: six mobile nodes, AODV, one CBR flow, and four scripted setdest moves.
  2. Work out from the geometry when each link should break and form.
  3. Run it; find the moves in the trace's M lines and the NAM file's movement records.
  4. Apply the TK_LIBRARY fix and animate the run in NAM.
  5. Draw the frames: positions, links and the route in use at 5, 21, 36 and 42 s.
  6. Print the timeline of links forming and breaking, and compare it with the predictions.
  7. Group the data packets by the route they took, and explain every change.
  8. Generate a random waypoint scenario with setdest, run ten nodes on it, and count the deliveries.
munotes.in130

Practical 11: A Basic MANET with Packet Animation

Observations

MeasuredValue
Links at the start0-1, 1-2, 2-5, 3-4
First route0-1-2-5, 66 packets, sent 1.00 to 17.25 s
Links 1-2 and 2-5 brokeat 17.5 s, as the geometry predicted
Packets lost at the break1 at node 1 (CBK), then 27 at node 0 (NRTE), sent 17.75 to 24.25 s
Links 1-4 and 4-5 formedat 25.5 s, as predicted
Second route0-1-4-5, 52 packets, sent 24.50 to 37.25 s
Link 0-1 brokeat 37.5 s, as predicted; 1 packet lost (CBK)
Third route0-3-4-5, 25 packets, sent 37.75 to 43.75 s
Delivered143 of 172, 83.1 per cent
Links that came or wentten, at five moments
Random waypoint run, 10 nodes, our setdest file183 of 184 delivered; another file gives other numbers

Result

A six-node MANET running AODV was simulated in NS-2 with four scripted setdest moves and animated in NAM, after NAM was made to start on Ubuntu 22.04 with a patched copy of Tk's library. Every link break and formation came when the geometry predicted: the links 1-2 and 2-5 broke at 17.5 s, the links through node 4 formed at 25.5 s, and the link 0-1 broke at 37.5 s. The route across the network followed: 0-1-2-5 until the first break, none during the partition, 0-1-4-5 once AODV found the new path, and 0-3-4-5 after the last break. 143 of 172 packets were delivered, 83.1 per cent; the 29 lost were the one in flight at each break and the 27 the source dropped when it could find no route. A random waypoint scenario for ten nodes was generated with setdest and run in the same way.

Where marks are lost

Expecting NAM to work as installed. On Ubuntu 22.04 it prints nam: and stops. Apply the fix in step 3 (Chapter 13's step 8) once, and note it in your journal as part of the setup.

Reading NAM upside down. NAM draws y upwards. A node given a larger Y_ is higher on the screen.

Looking for links in NAM. A wireless network has none to draw. Neighbours are nodes within 250 m, worked out from positions, as frames.py does.

Writing setdest before the node exists. $ns at 10.0 "$node(2) setdest ..." must come after set node(2) [$ns node], because the string is read when it is scheduled.

Sourcing a setdest file into a script with other names. The file speaks of ns_, node_ and god_. A script that calls them ns, node and nothing stops at the first line of the file.

munotes.in131

Practical 11: A Basic MANET with Packet Animation

Treating a random scenario's results as the answer. Another run of setdest gives another network. State the file, keep it, and compare protocols on the same file.

For the journal

Write: aim; what a MANET is and why its topology changes; manet.tcl and the table of its four moves with the times worked out from the geometry; the M lines and the NAM movement records, decoded; the NAM fix and one frame of the animation; the four frames from frames.py; the link timeline set against the predictions; the route groups from route.awk, with the explanation of each change; the setdest command, the size of the file it made, and the deliveries on it; observations; result.

Quick revision

  • MANET: no infrastructure, every node a router, nodes move, so the topology changes.
  • A link exists while two nodes are within 250 m (NS-2's default radio).
  • $ns at t "$node setdest x y speed": straight line, constant speed, stop at the point.
  • -movementTrace ON writes M lines: time, node, position, destination, speed.
  • NAM movement records: n -t <time> -s <node> -x -y -U -V -T, a velocity for a duration.
  • NAM on Ubuntu 22.04 needs TK_LIBRARY pointing at a copy of Tk's library without -encoding utf-8.
  • NAM draws y upwards and draws no wireless links.
  • The setdest generator: -v 1 -n nodes -p pause -M max speed -t time -x -y; random waypoint; a different file every run.
  • A script that sources a setdest file must use ns_, node_(i) and god_.
  • AODV repairs a route when it breaks; it does not switch to a better one while the old one works.

Questions you must be able to answer

1. What is a MANET, and why does its topology change? A mobile ad hoc network is a set of wireless nodes with no base station, in which every node also forwards other nodes' packets. The nodes move, so which nodes are within range of which, the topology, changes as they do.

2. What does $ns at 10.0 "$node(2) setdest 450 40 20" do? At 10 s of simulated time, node 2 starts moving in a straight line towards the point (450, 40) at 20 metres a second, and stops when it gets there.

3. How can you tell from positions alone whether two NS-2 nodes are neighbours? With the default radio, two nodes are neighbours when they are within 250 m of each other, the receive range worked out in Chapter 13.

munotes.in132

Practical 11: A Basic MANET with Packet Animation

4. Node 2 starts at (450, 300) and moves towards y = 40 at 20 m/s from 10 s. When does it lose node 1, at (250, 300)? When it is 250 m from node 1. The x difference is 200 m, so the y difference must reach 150 m, since 200² + 150² = 250². That takes 150 / 20 = 7.5 s, so the link breaks at 17.5 s.

5. Why does NAM print only nam: on Ubuntu 22.04, and how is it fixed? tclcl, which NAM loads first, replaces Tcl's source with a version that takes one argument, and Tk 8.6.12 loads its own files with source -encoding utf-8, so Tk fails to start. The fix is a copy of Tk's library with -encoding utf-8 removed, named by TK_LIBRARY when NAM starts.

6. What does an M line in the trace contain? The time, the node, its position then (x, y, z), the point it is moving to (x, y), and its speed.

7. Why were 27 packets lost at node 0 between 17.75 and 24.25 s, with the reason NRTE? Because the network was partitioned: no path led from node 0 to node 5. Node 0 held the packets while AODV searched for a route, and when the search gave up it dropped them with the reason "no route".

8. Node 3 came within range of node 0 at 35.5 s. Why did packets keep going through node 1 until 37.5 s? Because AODV keeps using a route while it works. It looks for a new one only when the old one breaks, and the link from 0 to 1 did not break until 37.5 s.

9. Why does NAM show no links between the nodes? Because a wireless network has no wires. Which nodes can hear each other is decided by distance, and NAM draws only the nodes and the packets.

10. What is the random waypoint model, and what does setdest -v 1 -n 10 -p 2 -M 10 -t 50 -x 500 -y 500 produce? Each node repeatedly picks a random point and a random speed, travels there, and pauses. The command writes the Tcl for ten nodes following that model for 50 s in a 500 m square, pausing 2 s at each point, at up to 10 m/s.

11. Why must a script that uses a setdest file call its simulator ns_ and its nodes node_(i)? Because those are the names the file's lines use. Any other names leave the file's first line pointing at a variable that does not exist, and the script stops.

12. Why should the setdest file be kept with the journal? Because setdest makes a different file every time it runs. Without the file, nobody, including you, can run the same experiment again.

Contents This chapter on its own page

munotes.in133

Chapter Fifteen

Practical 12: The AODV Routing Protocol

Syllabus topic Module 2, "Implementation of AODV Routing Protocol: Simulate the AODV routing protocol and analyze route discovery and route maintenance mechanisms."

Aim

To simulate the AODV routing protocol in NS-2 and to analyse, packet by packet, how it discovers a route and how it maintains routes when links break: route requests and replies, sequence numbers, route errors, the expanding ring search, retries, and local repair.

What you need to know before you start

AODV, Ad hoc On-Demand Distance Vector routing, is the MANET routing protocol of RFC 3561 and the one Practical 11 ran without looking inside. Its name says how it works.

  • On demand. A node looks for a route only when it has a packet for a destination it has no route to. Until then it knows nothing about the network and sends nothing about it. (DSDV, in Practical 14, is the opposite: every node keeps a route to every other node all the time.)
  • Distance vector. A route is a destination, the next hop towards it, and the number of hops. No node knows the whole path.

Four messages do all the work:

MessageSent byToWhat it does
route request (RREQ)a node needing a routeeveryone, by broadcast, passed on hop by hopasks for a route; each node it reaches learns a route back to its sender
route reply (RREP)the destination, or a node with a fresh enough routeback along the request's path, one hop at a timecarries the route; each node it passes learns a route to the destination
route error (RERR)a node whose next hop has goneits neighbourssays which destinations are now unreachable
HELLOevery node, every secondits neighbours"I am still here"; ns-2's AODV does not send them: it hears of a lost neighbour from its MAC instead

Sequence numbers keep routes fresh and loop-free. Every node has its own sequence number, which it raises when it sends a request or a reply, and every route carries its destination's number. A node accepts a route only if its number is newer than the one it has, or the same number with fewer hops (AODV::recvReply). An old route, which might lead in a circle, can never replace a newer one.

AODV is tuned by constants. RFC 3561 gives defaults; ns-2.35 uses its own (aodv/aodv.h). This practical measures ns-2's:

ConstantRFC 3561ns-2.35What it controls
TTL_START15the first ring of an expanding ring search
TTL_INCREMENT22how much each ring widens
TTL_THRESHOLD77the widest ring before the whole network is searched
NET_DIAMETER3530 (NETWORK_DIAMETER)the TTL of a network-wide request
RREQ_RETRIES23network-wide retries before giving up
NODE_TRAVERSAL_TIME40 ms30 msthe time a hop is assumed to take
ACTIVE_ROUTE_TIMEOUT3000 ms10 show long an unused route stays valid
MAX_RREQ_TIMEOUTnone10 show long ns-2 waits after giving up
munotes.in134

Practical 12: The AODV Routing Protocol

Step 1: the network

The network of Practical 11, with two more moves at the end so that one link breaks next to the destination. aodv.tcl:

# aodv.tcl: Practical 11's MANET, run on for a break next to the destination
set val(chan)   Channel/WirelessChannel
set val(prop)   Propagation/TwoRayGround
set val(netif)  Phy/WirelessPhy
set val(mac)    Mac/802_11
set val(ifq)    Queue/DropTail/PriQueue
set val(ll)     LL
set val(ant)    Antenna/OmniAntenna
set val(ifqlen) 50
set val(nn)     6
set val(rp)     AODV
set val(x)      800
set val(y)      600
set val(stop)   60.0

set ns [new Simulator]
set tf [open aodv.tr w]
$ns trace-all $tf
set nf [open aodv.nam w]
$ns namtrace-all-wireless $nf $val(x) $val(y)

set topo [new Topography]
$topo load_flatgrid $val(x) $val(y)
create-god $val(nn)

$ns node-config -adhocRouting $val(rp) -llType $val(ll) -macType $val(mac) \
    -ifqType $val(ifq) -ifqLen $val(ifqlen) -antType $val(ant) \
    -propType $val(prop) -phyType $val(netif) -channel [new $val(chan)] \
    -topoInstance $topo -agentTrace ON -routerTrace ON -macTrace OFF \
    -movementTrace ON

# where each node starts: x y
set start {
    {50 300}
    {250 300}
    {450 300}
    {250 560}
    {450 560}
    {650 300}
}
for {set i 0} {$i < $val(nn)} {incr i} {
    set node($i) [$ns node]
    $node($i) random-motion 0
    $node($i) set X_ [lindex $start $i 0]
    $node($i) set Y_ [lindex $start $i 1]
    $node($i) set Z_ 0
    $ns initial_node_pos $node($i) 30
}

# how the topology changes: at time t, node n heads for (x, y) at speed m/s
$ns at 10.0 "$node(2) setdest 450 40 20"
$ns at 20.0 "$node(4) setdest 450 300 20"
$ns at 30.0 "$node(1) setdest 250 40 20"
$ns at 30.0 "$node(3) setdest 250 300 20"
$ns at 40.0 "$node(2) setdest 550 150 20"
$ns at 44.0 "$node(4) setdest 380 300 10"

set udp [new Agent/UDP]
$ns attach-agent $node(0) $udp
set sink [new Agent/Null]
$ns attach-agent $node(5) $sink
$ns connect $udp $sink
set cbr [new Application/Traffic/CBR]
$cbr set packetSize_ 512
$cbr set interval_ 0.25
$cbr attach-agent $udp
$ns at 1.0 "$cbr start"
$ns at 59.0 "$cbr stop"

for {set i 0} {$i < $val(nn)} {incr i} {
    $ns at $val(stop) "$node($i) reset"
}
$ns at $val(stop) "finish"
proc finish {} {
    global ns tf nf
    $ns flush-trace
    close $tf
    close $nf
    exit 0
}
$ns run
TimeMoveWhat it does
10 snode 2 leaves the linelinks 1-2 and 2-5 break at 17.5 s
20 snode 4 heads for the gaplinks 1-4 and 4-5 form at 25.5 s
30 snode 1 leaves, node 3 heads for the linelink 0-3 forms at 35.5 s, link 0-1 breaks at 37.5 s
40 snode 2 heads for (550, 150), between nodes 4 and 5it is within range of both by 44 s
44 snode 4 backs away from node 5 at 10 m/slink 4-5 breaks at 49 s, when node 4 passes x = 400
munotes.in135

Practical 12: The AODV Routing Protocol

So one run shows AODV finding a route, losing it to a break in the middle, finding none, finding one again, losing it to a break at the source, and repairing a break next to the destination.

Step 2: run it, and list every control packet

$ ns aodv.tcl
num_nodes is set 6
INITIALIZE THE LIST xListHead
channel.cc:sendUp - Calc highestAntennaZ_ and distCST_
highestAntennaZ_ = 1.5,  distCST_ = 550.0
SORTING LISTS ...DONE!

Everything AODV did is in its control packets. control.awk prints each one that a node sent or forwarded, with its fields named:

# control.awk: every AODV control packet sent or forwarded, with its fields named
function node(f) { return substr(f, 2, length(f) - 2) }
function inner(f) { gsub(/[][]/, "", f); return f }
$4 == "RTR" && $7 == "AODV" && ($1 == "s" || $1 == "f") {
    if ($NF == "(REQUEST)")
        printf "%10.6f  node %s  REQUEST  TTL %-2s  hops %s  id %s  for %s (seq %s)  from %s (seq %s)\n",
            $2, node($3), $16, $19, $20, inner($21), inner($22), inner($23), inner($24)
    else if ($NF == "(REPLY)")
        printf "%10.6f  node %s  REPLY    to %s  hops %s  route to %s (seq %s)\n",
            $2, node($3), inner($17), $19, inner($20), inner($21)
    else if ($NF == "(ERROR)")
        printf "%10.6f  node %s  ERROR    to all neighbours: %s unreachable\n", $2, node($3), inner($20)
}

The field numbers come from Chapter 13's decoding of the wireless trace. In a request, [0x2 1 1 [5 0] [0 4]] is the type (2, a request), the hop count so far, the request's id, then the destination with the newest sequence number the sender knows for it, then the source with its own sequence number. In a reply, [0x4 1 [5 4] 10.000000] is the type (4), the hop count, the destination with its sequence number, and the route's lifetime in seconds.

$ awk -f control.awk aodv.tr
  1.000000  node 0  REQUEST  TTL 30  hops 1  id 1  for 5 (seq 0)  from 0 (seq 4)
  1.001551  node 1  REQUEST  TTL 29  hops 2  id 1  for 5 (seq 0)  from 0 (seq 4)
  1.010791  node 2  REQUEST  TTL 28  hops 3  id 1  for 5 (seq 0)  from 0 (seq 4)
  1.011900  node 5  REPLY    to 2  hops 1  route to 5 (seq 4)
  1.016547  node 2  REPLY    to 1  hops 2  route to 5 (seq 4)
  1.021579  node 1  REPLY    to 0  hops 3  route to 5 (seq 4)
 17.546547  node 1  ERROR    to all neighbours: 5 unreachable
 17.750000  node 0  REQUEST  TTL 5   hops 1  id 2  for 5 (seq 5)  from 0 (seq 6)
 17.752923  node 1  REQUEST  TTL 4   hops 2  id 2  for 5 (seq 5)  from 0 (seq 6)
 18.000000  node 0  REQUEST  TTL 7   hops 1  id 3  for 5 (seq 5)  from 0 (seq 8)
 18.005354  node 1  REQUEST  TTL 6   hops 2  id 3  for 5 (seq 5)  from 0 (seq 8)
 18.250000  node 0  REQUEST  TTL 30  hops 1  id 4  for 5 (seq 5)  from 0 (seq 10)
 18.260515  node 1  REQUEST  TTL 29  hops 2  id 4  for 5 (seq 5)  from 0 (seq 10)
 19.000000  node 0  REQUEST  TTL 30  hops 1  id 5  for 5 (seq 5)  from 0 (seq 12)
 19.006283  node 1  REQUEST  TTL 29  hops 2  id 5  for 5 (seq 5)  from 0 (seq 12)
 20.250000  node 0  REQUEST  TTL 30  hops 1  id 6  for 5 (seq 5)  from 0 (seq 14)
 20.259229  node 1  REQUEST  TTL 29  hops 2  id 6  for 5 (seq 5)  from 0 (seq 14)
 22.000000  node 0  REQUEST  TTL 30  hops 1  id 7  for 5 (seq 5)  from 0 (seq 16)
 22.002802  node 1  REQUEST  TTL 29  hops 2  id 7  for 5 (seq 5)  from 0 (seq 16)
 34.250000  node 0  REQUEST  TTL 30  hops 1  id 8  for 5 (seq 5)  from 0 (seq 18)
 34.255385  node 1  REQUEST  TTL 29  hops 2  id 8  for 5 (seq 5)  from 0 (seq 18)
 34.258353  node 4  REQUEST  TTL 28  hops 3  id 8  for 5 (seq 5)  from 0 (seq 18)
 34.259781  node 5  REPLY    to 4  hops 1  route to 5 (seq 6)
 34.265449  node 4  REPLY    to 1  hops 2  route to 5 (seq 6)
 34.270701  node 1  REPLY    to 0  hops 3  route to 5 (seq 6)
 37.538287  node 0  ERROR    to all neighbours: 5 unreachable
 37.750000  node 0  REQUEST  TTL 5   hops 1  id 9  for 5 (seq 7)  from 0 (seq 22)
 37.756271  node 3  REQUEST  TTL 4   hops 2  id 9  for 5 (seq 7)  from 0 (seq 22)
 37.759735  node 4  REQUEST  TTL 3   hops 3  id 9  for 5 (seq 7)  from 0 (seq 22)
 37.761103  node 5  REPLY    to 4  hops 1  route to 5 (seq 8)
 37.762837  node 4  REPLY    to 3  hops 2  route to 5 (seq 8)
 37.767870  node 3  REPLY    to 0  hops 3  route to 5 (seq 8)
 49.047548  node 4  REQUEST  TTL 30  hops 1  id 1  for 5 (seq 8)  from 4 (seq 4)
 49.048817  node 3  REPLY    to 4  hops 3  route to 5 (seq 8)
 49.057633  node 2  REQUEST  TTL 29  hops 2  id 1  for 5 (seq 8)  from 4 (seq 4)
 49.058662  node 5  REPLY    to 2  hops 1  route to 5 (seq 10)
 49.060575  node 2  REPLY    to 4  hops 2  route to 5 (seq 10)
munotes.in136

Practical 12: The AODV Routing Protocol

Thirty-seven packets in sixty seconds, in five groups. The rest of the practical reads them one group at a time.

munotes.in137

Practical 12: The AODV Routing Protocol

Step 3: route discovery

At 1.0 s node 0 has a packet for node 5 and no route. The first six lines are the discovery.

The request floods outwards. Node 0 broadcasts a request with hop count 1 and id 1. Node 1 hears it, and rebroadcasts it with hop count 2 and a TTL one lower; node 2 does the same with hop count 3. Nodes 3 and 4 are out of range and never hear it. Node 0 hears node 1's rebroadcast of its own request, and node 1 hears node 2's; each discards it without a trace line (AODV::recvRequest frees its own requests and any it has seen before). A node passes on each request, identified by its source and id, once.

Why TTL 30, when TTL_START is 5? AODV::sendRequest chooses the TTL from the largest of the last TTL it used and the route's last known hop count. For a destination it has never had a route to, the hop count is still its starting value, 255, "unknown" (aodv_rtable.cc, line 47), which is above TTL_THRESHOLD, so the very first request of all goes to the whole network. The expanding ring is used only when a hop count is known, after a route breaks (step 5).

The destination answers, and raises its sequence number. Node 5 replies with sequence number 4. The request carried 0 for node 5, meaning "I know nothing"; the rule in recvRequest sets the reply's number to one more than the larger of the two, rounded up to an even number: node 5's own 2 and the request's 0 give 3, and 3 rounds up to 4. The reply goes back along the path the request came by, node 2, then node 1, then node 0, its hop count rising from 1 to 3.

Each node learns a route from each message it accepts. A request teaches the route back to its source; a reply teaches the route forward to the destination. table.awk rebuilds what each node learnt, by AODV's own acceptance rule, from the trace:

# table.awk: the routes each node accepts from the requests and replies it hears, by AODV's
# own rule: a newer destination sequence number, or the same one with fewer hops.
# Every line of the trace is read; only those from time `from` to `to` are printed.
#   awk -v from=T1 -v to=T2 -f table.awk trace
function node(f) { return substr(f, 2, length(f) - 2) }
function inner(f) { gsub(/[][]/, "", f); return f }
function offer(me, dst, via, h, s, how,    k, note) {
    k = me SUBSEP dst; h += 0; s += 0
    if (!(k in seq) || seq[k] < s || (seq[k] == s && hops[k] > h)) {
        seq[k] = s; hops[k] = h; verb = "accepts"; note = how
    } else {
        verb = "ignores"; note = sprintf("%s; it has seq %d, %d hops", how, seq[k], hops[k])
    }
    if ($2 >= from && $2 < to)
        printf "%10.6f  node %s  %s  route to %s via %s, %d hops, seq %d   (%s)\n", $2, me, verb, dst, via, h, s, note
}
$1 == "r" && $4 == "RTR" && $7 == "AODV" {
    me = node($3); prev = $11
    if ($NF == "(REQUEST)") {
        src = inner($23); id = $20
        if (src == me || (me, src, id) in heard) next    # its own request, or a copy already heard
        heard[me, src, id] = 1
        offer(me, src, prev, $19, inner($24), "reverse, from a request")
    } else if ($NF == "(REPLY)")
        offer(me, inner($20), prev, $19, inner($21), "forward, from a reply")
}
munotes.in138

Practical 12: The AODV Routing Protocol

The previous hop, "via", is the MAC transmitter address, field 11 of the trace line (Chapter 13, step 6).

$ awk -v from=1 -v to=2 -f table.awk aodv.tr
  1.001409  node 1  accepts  route to 0 via 0, 1 hops, seq 4   (reverse, from a request)
  1.002799  node 2  accepts  route to 0 via 1, 2 hops, seq 4   (reverse, from a request)
  1.011900  node 5  accepts  route to 0 via 2, 3 hops, seq 4   (reverse, from a request)
  1.016547  node 2  accepts  route to 5 via 5, 1 hops, seq 4   (forward, from a reply)
  1.021579  node 1  accepts  route to 5 via 2, 2 hops, seq 4   (forward, from a reply)
  1.026631  node 0  accepts  route to 5 via 1, 3 hops, seq 4   (forward, from a reply)

When node 0 accepts its route to 5, 3 hops via node 1, the discovery is over, and the packet that waited for it goes. From request to route took 26.6 ms, a little under 9 ms a hop; AODV remembers that figure and uses it in step 5.

Step 4: a link breaks in the middle

At 17.5 s node 2 moves out of range of nodes 1 and 5. Nothing announces it: ns-2's AODV sends no HELLOs. The break is found when node 1 tries to pass on the next data packet. The 802.11 MAC tries to send it, hears no answer from node 2, retries, gives up about 41 ms later, and tells AODV through a callback that the link has failed: the packet is dropped with the reason CBK.

munotes.in139

Practical 12: The AODV Routing Protocol

AODV::rt_ll_failed then decides between two responses. If the packet has already travelled further than the remaining distance to its destination, it tries to repair the route on the spot; otherwise it gives the route up. At node 1 the packet had come one hop and had two to go, so it gave up: it raised its sequence number for node 5 from 4 to 5, marked the route down, and broadcast a route ERROR to its neighbours, TTL 1. Node 0, hearing it, marks its own route to 5 down too.

The ERROR line needs care. ns-2 prints an error through the layout of a reply, so in [0x8 1 [5 0] 0.000000] only the first three numbers mean anything: type 8, one unreachable destination, and that destination, node 5. The 0 and 0.000000 are the reply layout reading parts of the error packet that hold nothing.

Step 5: searching again, and giving up

Node 0's next data packet, at 17.75 s, has no route, so AODV asks again, and the next nine request lines are that search. It is not repeated blindly; each request follows from the one before by rules in AODV::sendRequest:

  1. The expanding ring. The route's last hop count is now known, 3, so the first request goes only 3 + 2 = 5 hops. If nothing answers, the next goes 5 + 2 = 7. At TTL_THRESHOLD, 7, the next goes to the whole network, TTL 30.
  2. Each request waits for 2 × TTL × the time a hop takes, measured in the last discovery, and each network-wide request waits that times the number of network-wide requests so far.
  3. Requests are triggered by data. AODV asks when it has a packet to route and its wait is over, so requests fall on the data packets' quarter seconds.
  4. When it has sent more network-wide requests than RREQ_RETRIES, 3, without an answer, it gives up: it drops every packet it was holding for the destination, reason NRTE, no route, and asks nothing more for MAX_RREQ_TIMEOUT, 10 s.

schedule.py applies these rules, and only these, to predict the search:

# schedule.py: when AODV at node 0 asks again for a route after a break, by its own rules
# (aodv/aodv.cc, AODV::sendRequest; the constants are aodv/aodv.h's)
import sys

TTL_START, TTL_INCREMENT, TTL_THRESHOLD, NETWORK_DIAMETER = 5, 2, 7, 30
RREQ_RETRIES, MAX_RREQ_TIMEOUT = 3, 10.0
INTERVAL = 0.25                     # a new data packet every quarter second


def first_discovery(trace):
    """Node 0's first request and the reply that answered it."""
    sent = None
    for line in open(trace):
        f = line.split()
        if len(f) < 8 or f[6] != 'AODV' or f[2] != '_0_':
            continue
        if sent is None and f[0] == 's' and f[-1] == '(REQUEST)':
            sent = float(f[1])
        elif sent is not None and f[0] == 'r' and f[-1] == '(REPLY)':
            return sent, float(f[1]), int(f[18])


sent, got, hops = first_discovery(sys.argv[1])
per_hop = (got - sent) / hops
print('first discovery: asked at %.6f s, answered at %.6f s, %d hops: %.6f s a hop'
      % (sent, got, hops, per_hop))

last_ttl = last_hops = hops         # what the route remembers once it breaks
count, not_before = 0, 0.0
t, stop = float(sys.argv[2]), float(sys.argv[3])
while t <= stop:
    if t >= not_before:
        if count > RREQ_RETRIES:
            count, not_before = 0, t + MAX_RREQ_TIMEOUT
            print('%6.2f s  gives up: every waiting packet dropped, no request before %.2f s'
                  % (t, not_before))
        else:
            m = max(last_ttl, last_hops)
            if m == 0:
                ttl = TTL_START
            elif m < TTL_THRESHOLD:
                ttl = m + TTL_INCREMENT
            else:
                ttl = NETWORK_DIAMETER
                count += 1
            last_ttl = ttl
            wait = min(2 * ttl * per_hop * max(count, 1), MAX_RREQ_TIMEOUT)
            not_before = t + wait
            print('%6.2f s  request, TTL %2d; no other before %.3f s' % (t, ttl, not_before))
    t = round(t + INTERVAL, 2)
munotes.in140

Practical 12: The AODV Routing Protocol

$ python3 schedule.py aodv.tr 17.75 34.25
first discovery: asked at 1.000000 s, answered at 1.026631 s, 3 hops: 0.008877 s a hop
 17.75 s  request, TTL  5; no other before 17.839 s
 18.00 s  request, TTL  7; no other before 18.124 s
 18.25 s  request, TTL 30; no other before 18.783 s
 19.00 s  request, TTL 30; no other before 20.065 s
 20.25 s  request, TTL 30; no other before 21.848 s
 22.00 s  request, TTL 30; no other before 24.130 s
 24.25 s  gives up: every waiting packet dropped, no request before 34.25 s
 34.25 s  request, TTL 30; no other before 34.783 s

Set it against node 0's requests in the trace:

$ awk -f control.awk aodv.tr | awk '$3 == 0 && $4 == "REQUEST"'
  1.000000  node 0  REQUEST  TTL 30  hops 1  id 1  for 5 (seq 0)  from 0 (seq 4)
 17.750000  node 0  REQUEST  TTL 5   hops 1  id 2  for 5 (seq 5)  from 0 (seq 6)
 18.000000  node 0  REQUEST  TTL 7   hops 1  id 3  for 5 (seq 5)  from 0 (seq 8)
 18.250000  node 0  REQUEST  TTL 30  hops 1  id 4  for 5 (seq 5)  from 0 (seq 10)
 19.000000  node 0  REQUEST  TTL 30  hops 1  id 5  for 5 (seq 5)  from 0 (seq 12)
 20.250000  node 0  REQUEST  TTL 30  hops 1  id 6  for 5 (seq 5)  from 0 (seq 14)
 22.000000  node 0  REQUEST  TTL 30  hops 1  id 7  for 5 (seq 5)  from 0 (seq 16)
 34.250000  node 0  REQUEST  TTL 30  hops 1  id 8  for 5 (seq 5)  from 0 (seq 18)
 37.750000  node 0  REQUEST  TTL 5   hops 1  id 9  for 5 (seq 7)  from 0 (seq 22)
munotes.in141

Practical 12: The AODV Routing Protocol

Every request after the break is where the rules put it, with the TTL they give: 5, 7, then 30 four times at widening intervals, then nothing for ten seconds. The network was partitioned from 17.5 to 25.5 s, so every one of the search's requests went unanswered, and at 24.25 s the source dropped the 27 packets it had been holding. The route through node 4 existed from 25.5 s, but AODV was in its ten-second hold-down; its next request, at 34.25 s, found the route at once, and the packets that had waited since 24.5 s were delivered. The request at 34.25 s is network-wide because the last TTL used was 30.

Step 6: a link breaks at the source

At 37.5 s the link from node 0 to node 1 breaks. This time node 0 itself finds out, when its own packet fails (CBK). It is the source, so the packet has travelled no distance at all: no repair. Node 0 broadcasts an ERROR, and at its next data packet, 37.75 s, searches again. The last discovery ended with a 3-hop route, so the ring starts at 3 + 2 = 5, and it is enough: nodes 3 and 4 pass the request on, node 5 replies with sequence number 8, and 23 ms after the request node 0 has the route 0-3-4-5. One packet was lost to this break, the one in flight.

Step 7: local repair next to the destination

At 49 s node 4, backing away, loses node 5. The packet that finds the break has come two hops, 0 to 3 to 4, and has one to go, so this time rt_ll_failed repairs: node 4 keeps the packet, sends no ERROR, and asks for a route to 5 itself. Its request goes network-wide, TTL 30: node 4 never had its route to 5 break before, so its last hop count is still "unknown", 255.

Two answers come back, and they show what sequence numbers are for.

$ awk -v from=49 -v to=50 -f table.awk aodv.tr
 49.048817  node 3  accepts  route to 4 via 4, 1 hops, seq 4   (reverse, from a request)
 49.048817  node 2  accepts  route to 4 via 4, 1 hops, seq 4   (reverse, from a request)
 49.054237  node 4  ignores  route to 5 via 3, 3 hops, seq 8   (forward, from a reply; it has seq 8, 1 hops)
 49.058662  node 5  accepts  route to 4 via 2, 2 hops, seq 4   (reverse, from a request)
 49.060575  node 2  accepts  route to 5 via 5, 1 hops, seq 10   (forward, from a reply)
 49.065787  node 4  accepts  route to 5 via 2, 2 hops, seq 10   (forward, from a reply)
munotes.in142

Practical 12: The AODV Routing Protocol

Node 3 answers first, with a stale route. Node 3's route to node 5 runs through node 4, the node that is asking; accepting it would send packets in a circle. Node 3 is allowed to answer, because its route is as fresh as the request asks, sequence number 8. But node 4 already has number 8 for node 5, with 1 hop, and a route with the same number and 3 hops is not better. Node 4 ignores it.

Node 5 answers through node 2, with a new number. Node 5 raises its sequence number to 10 in the reply, newer than anything anyone had, and every node on the way accepts it. Node 4's new route is 2 hops, via node 2, and the held packet goes that way. Nothing was lost: the route is now 0-3-4-2-5.

Step 8: what it cost, and what it delivered

route.awk, from Practical 11, groups the data packets by the route each took:

# route.awk: the route every data packet took, grouped into runs of packets that went the same way
$7 == "cbr" && $1 == "s" && $4 == "AGT" { t[$6] = $2; path[$6] = "0"; ids[++n] = $6 }
$7 == "cbr" && $1 == "f" && $4 == "RTR" { path[$6] = path[$6] "-" substr($3, 2, length($3) - 2) }
$7 == "cbr" && $1 == "r" && $4 == "AGT" { got[$6] = 1; path[$6] = path[$6] "-5" }
$7 == "cbr" && $1 == "D" { why[$6] = $5 }
END {
    for (i = 1; i <= n; i++) {
        id = ids[i]
        how = (id in got) ? "delivered via " path[id] : "LOST (" why[id] ") at node " substr(path[id], length(path[id]))
        if (how != last) {
            if (i > 1) printf "%6.2f to %6.2f s  %3d packets  %s\n", first, t[ids[i - 1]], count, last
            first = t[id]; count = 0; last = how
        }
        count++
    }
    printf "%6.2f to %6.2f s  %3d packets  %s\n", first, t[ids[n]], count, last
}
$ awk -f route.awk aodv.tr
  1.00 to  17.25 s   66 packets  delivered via 0-1-2-5
 17.50 to  17.50 s    1 packets  LOST (CBK) at node 1
 17.75 to  24.25 s   27 packets  LOST (NRTE) at node 0
 24.50 to  37.25 s   52 packets  delivered via 0-1-4-5
 37.50 to  37.50 s    1 packets  LOST (CBK) at node 0
 37.75 to  48.75 s   45 packets  delivered via 0-3-4-5
 49.00 to  49.00 s    1 packets  delivered via 0-3-4-4-2-5
 49.25 to  58.75 s   39 packets  delivered via 0-3-4-2-5
munotes.in143

Practical 12: The AODV Routing Protocol

The packet sent at 49.00 s is the one node 4 held during the repair: it appears as forwarded by node 4 twice, once into the broken link and once, after the repair, to node 2.

The control traffic, counted by type:

$ awk -f control.awk aodv.tr | awk '{print $4}' | sort | uniq -c
      2 ERROR
     12 REPLY
     23 REQUEST

Procedure

  1. Write aodv.tcl: Practical 11's six nodes, AODV, one CBR flow, and six scripted moves.
  2. Run it and list every AODV control packet with control.awk.
  3. Read the first discovery: the request's flood, the destination's reply, and the routes each node learnt (table.awk).
  4. Find the first link break: the CBK drop, the route ERROR, and why no repair was tried.
  5. Predict the search that follows with schedule.py, and compare it with the trace.
  6. Read the break at the source and the search that followed it.
  7. Read the local repair: the request, the stale reply ignored, and the fresh one accepted.
  8. Group the data packets by route with route.awk, and count the control packets by type.

Observations

MeasuredValue
First discoveryrequest at 1.000 s, network-wide (TTL 30); route at node 0 at 1.026631 s, 3 hops, 8.9 ms a hop
Node 5's sequence number in its replies4, then 6, 8 and 10
Break at 17.5 sCBK at node 1 after about 41 ms of retries; ERROR from node 1
Search after itTTL 5, 7, 30, 30, 30, 30, at 17.75, 18.00, 18.25, 19.00, 20.25 and 22.00 s, as schedule.py predicted
Given upat 24.25 s: 27 packets dropped with NRTE; next request at 34.25 s
Break at the source, 37.5 sERROR from node 0; route 0-3-4-5 found in 23 ms by a TTL 5 request
Local repair, 49 sby node 4, TTL 30; node 3's stale reply ignored; route 0-3-4-2-5; nothing lost
Control packets23 requests, 12 replies, 2 errors: 37
Data packets203 of 232 delivered, 87.5 per cent

Result

AODV was simulated in NS-2 on a six-node MANET whose links broke and formed on a known schedule, and every one of its 37 control packets was read. Route discovery flooded a request, network-wide the first time, and brought back a reply from the destination with a raised sequence number; every node on the path learnt a reverse route from the request and a forward route from the reply. Route maintenance showed three responses to a broken link: a route ERROR when the break was nearer the source (17.5 s and 37.5 s), and a local repair, with no error, when it was next to the destination (49 s). After the first break the source's search followed AODV's rules exactly, as predicted by a program applying them: an expanding ring of TTL 5 and 7, four network-wide requests at growing intervals, then a give-up that dropped 27 packets and a ten-second hold-down. In the local repair, a stale reply that would have led back through the repairing node was ignored because its sequence number was no newer, and the destination's fresh reply was accepted. 203 of 232 data packets were delivered, 87.5 per cent.

munotes.in144

Practical 12: The AODV Routing Protocol

Where marks are lost

Saying that ns-2's AODV sends HELLO messages. It does not. It learns of a broken link from its MAC, when a packet cannot be delivered: the CBK drop.

Reading the ERROR line like a reply. Only the type, the count and the unreachable destination mean anything. The 0 and 0.000000 after them are the reply layout reading an error packet.

Expecting the first request to use TTL_START. In ns-2 a destination never reached before has hop count 255, "unknown", so the first request goes to the whole network. The expanding ring starts only after a route breaks.

Counting every REQUEST line as a new search. A request passed on by other nodes keeps its source and id. Node 0's search after the break is six requests, not twelve lines.

Saying AODV moves to a better route. It does not look for one while its route works. It replaces a route only when the route breaks.

Confusing when a packet was dropped with when it was sent. The 27 NRTE drops all happen at 24.25 s, for packets sent from 17.75 s on. Group losses by the time the packet was sent.

Quoting RFC 3561's constants for an ns-2 run. ns-2.35 uses its own: TTL_START 5 against 1, a network diameter of 30 against 35, three retries against two.

For the journal

Write: aim; what AODV is, in five lines; the table of its four messages; the table of constants, RFC against ns-2; aodv.tcl and its moves; the list of control packets; the first discovery read line by line, with the routes from table.awk; the first break and its ERROR; schedule.py's prediction set against the trace, with the rules; the break at the source; the local repair and the ignored reply; the route groups and the control counts; observations; result.

Quick revision

  • AODV: on demand (a route only when needed), distance vector (next hop and hop count only).
  • Request: type 2, hop count, id, destination with the newest known sequence number, source with its own.
  • Reply: type 4, hop count, destination with its sequence number, lifetime.
  • A request teaches the route back to its source; a reply teaches the route to the destination.
  • Accept a route only if its sequence number is newer, or the same with fewer hops.
  • A node passes on a request (source, id) once; its own and repeated ones are discarded.
  • ns-2's AODV has no HELLOs: a broken link is reported by the MAC (CBK).
  • Break nearer the source: ERROR to the neighbours. Break nearer the destination: local repair, no ERROR.
  • Search after a break: TTL last hops + 2, then + 2 again, then 30; waits of 2 × TTL × time per hop, times the number of network-wide tries.
  • More than RREQ_RETRIES (3) network-wide tries: drop the waiting packets (NRTE), then 10 s of silence.
  • First request to a new destination: network-wide, because the hop count is unknown.
munotes.in145

Practical 12: The AODV Routing Protocol

Questions you must be able to answer

1. Why is AODV called on-demand? Because a node looks for a route only when it has a packet for a destination it has no route to. Until then it sends nothing about routes to that destination.

2. What does a route request carry? Its type; the number of hops it has travelled; an id that, with the source's address, identifies it; the destination, with the newest sequence number the source knows for it; and the source, with its own sequence number.

3. How does a node avoid passing on the same request twice? It remembers the source and id of every request it has passed on, and discards any copy it hears again, and any request it sent itself.

4. What does a node learn from a request, and what from a reply? From a request, a route back to the request's source, through the node it heard it from. From a reply, a route to the destination, through the node it heard it from.

5. What is a destination sequence number for? Use the reply node 3 sent at 49 s. It says how fresh a route is, so that no node replaces a route with an older one, which might lead in a circle. Node 3's route to node 5 went through node 4 itself and carried number 8; node 4 already had 8 with 1 hop, so it ignored node 3's route and accepted node 5's reply, which carried 10.

6. How does ns-2's AODV learn that a link has broken? From its MAC: when a packet cannot be delivered to the next hop after the MAC's retries, the MAC reports the failure and the packet is dropped with the reason CBK.

7. When does AODV repair a route locally, and when does it send an ERROR? Give both cases from the run. It repairs when the packet has already come further than the distance left to its destination, and sends an ERROR otherwise. At 17.5 s node 1 had a packet that had come 1 hop with 2 to go: ERROR. At 49 s node 4 had one that had come 2 hops with 1 to go: local repair.

munotes.in146

Practical 12: The AODV Routing Protocol

8. Why was node 0's first request sent with TTL 30, but its first request after the break with TTL 5? The first time, its hop count to node 5 was unknown, stored as 255, so the request went to the whole network. After the break it knew the route had been 3 hops, and the expanding ring starts at 3 + 2 = 5.

9. Why did node 0 send no request from 24.25 s to 34.25 s, when a route existed from 25.5 s? Because at 24.25 s it had sent more network-wide requests than RREQ_RETRIES allows without an answer, so it gave up and, in ns-2, waits MAX_RREQ_TIMEOUT, 10 s, before asking again.

10. What happened to the packets node 0 held at 24.25 s, and to those it was given afterwards? The 27 it held were dropped with the reason NRTE, no route. Those sent from 24.50 s on were held again and delivered after the request at 34.25 s found the route through node 4.

11. Which constants decided the timing of the search after the break? TTL_INCREMENT and TTL_THRESHOLD (the ring of 5 and 7), NETWORK_DIAMETER (30), RREQ_RETRIES (3) and MAX_RREQ_TIMEOUT (10 s), with the time per hop measured in the first discovery.

12. Why is the local repair's request at 49 s network-wide? Because ns-2's local repair sends an ordinary request, and node 4's route to node 5 had never broken before, so its last hop count was still unknown, 255.

Contents This chapter on its own page

munotes.in147

Chapter Sixteen

Practical 13: The DSR Routing Protocol and Its Overhead against AODV

Syllabus topic Module 2, "Implementation of DSR Routing Protocol: Simulate the DSR protocol and compare routing overhead with AODV."

Aim

To simulate the DSR routing protocol in NS-2, to follow how it discovers, caches and maintains routes, and to compare its routing overhead with AODV's on exactly the same network, movements and traffic.

What you need to know before you start

DSR, Dynamic Source Routing (RFC 4728), is the other classic on-demand MANET protocol. Like AODV it looks for a route only when it needs one, and sends nothing periodically. Its difference is in the name: source routing. The source puts the whole route, every node in order, into the packet's header, and each node on the way just passes the packet to the next address on the list. No node needs a routing table entry for anyone else's traffic.

Route discovery works like AODV's, with one change: the request records the route as it goes. Each node that passes a request on adds its own address to it, so when the request reaches the destination it carries the complete path, and the destination's reply simply sends that path back to the source.

The route cache is DSR's other half. A DSR node keeps every route it learns, several per destination, and learns them not only from replies to its own requests but from every source route it forwards and, in ns-2 with the "tap" on, from packets it merely overhears. So a node can often answer a request from its cache, without the request travelling to the destination at all.

Route maintenance. A node that cannot deliver a packet to the next address sends a route error back to the source, naming the broken link, and every node that hears it removes the routes that use it. The node holding the packet may salvage it: send it on by another route from its own cache.

Overhead is what routing costs, and this practical measures it four ways:

MeasureWhat it counts
routing packetsevery control packet sent or forwarded, by every node
routing bytestheir sizes, added up
routing packets per delivered data packetthe cost of each packet that arrived: the usual comparison, called the normalized routing load
data packet size on the airwhat a source route adds to every data packet

Step 1: one script for both protocols

A fair comparison changes one thing only. compare.tcl is Practical 12's network, moves and traffic, with the routing protocol taken from the command line: ns compare.tcl AODV or ns compare.tcl DSR. Each run names its files after the protocol.

# compare.tcl: Practical 12's network and moves, routed by the protocol named on the command line
#   ns compare.tcl AODV    or    ns compare.tcl DSR
set val(chan)   Channel/WirelessChannel
set val(prop)   Propagation/TwoRayGround
set val(netif)  Phy/WirelessPhy
set val(mac)    Mac/802_11
set val(ifq)    Queue/DropTail/PriQueue
set val(ll)     LL
set val(ant)    Antenna/OmniAntenna
set val(ifqlen) 50
set val(nn)     6
set val(rp)     [lindex $argv 0]
if {$val(rp) == "DSR"} {
    set val(ifq) CMUPriQueue           ;# DSR works only with this queue
}
set val(x)      800
set val(y)      600
set val(stop)   60.0

set ns [new Simulator]
set name [string tolower $val(rp)]
set tf [open $name.tr w]
$ns trace-all $tf
set nf [open $name.nam w]
$ns namtrace-all-wireless $nf $val(x) $val(y)

set topo [new Topography]
$topo load_flatgrid $val(x) $val(y)
create-god $val(nn)

$ns node-config -adhocRouting $val(rp) -llType $val(ll) -macType $val(mac) \
    -ifqType $val(ifq) -ifqLen $val(ifqlen) -antType $val(ant) \
    -propType $val(prop) -phyType $val(netif) -channel [new $val(chan)] \
    -topoInstance $topo -agentTrace ON -routerTrace ON -macTrace OFF \
    -movementTrace ON

# where each node starts: x y
set start {
    {50 300}
    {250 300}
    {450 300}
    {250 560}
    {450 560}
    {650 300}
}
for {set i 0} {$i < $val(nn)} {incr i} {
    set node($i) [$ns node]
    $node($i) random-motion 0
    $node($i) set X_ [lindex $start $i 0]
    $node($i) set Y_ [lindex $start $i 1]
    $node($i) set Z_ 0
    $ns initial_node_pos $node($i) 30
}

# how the topology changes: at time t, node n heads for (x, y) at speed m/s
$ns at 10.0 "$node(2) setdest 450 40 20"
$ns at 20.0 "$node(4) setdest 450 300 20"
$ns at 30.0 "$node(1) setdest 250 40 20"
$ns at 30.0 "$node(3) setdest 250 300 20"
$ns at 40.0 "$node(2) setdest 550 150 20"
$ns at 44.0 "$node(4) setdest 380 300 10"

set udp [new Agent/UDP]
$ns attach-agent $node(0) $udp
set sink [new Agent/Null]
$ns attach-agent $node(5) $sink
$ns connect $udp $sink
set cbr [new Application/Traffic/CBR]
$cbr set packetSize_ 512
$cbr set interval_ 0.25
$cbr attach-agent $udp
$ns at 1.0 "$cbr start"
$ns at 59.0 "$cbr stop"

for {set i 0} {$i < $val(nn)} {incr i} {
    $ns at $val(stop) "$node($i) reset"
}
$ns at $val(stop) "finish"
proc finish {} {
    global ns tf nf
    $ns flush-trace
    close $tf
    close $nf
    exit 0
}
$ns run
munotes.in148

Practical 13: The DSR Routing Protocol and Its Overhead against AODV

$argv is the list of words after the script's name. The if is the one line DSR needs that AODV does not.

Step 2: why DSR needs its own queue

Try DSR with the queue every other protocol uses, by turning the one DSR line into a comment:

$ sed 's/set val(ifq) CMUPriQueue/# &/' compare.tcl > wrong.tcl
$ grep -n "CMUPriQueue" wrong.tcl
14:    # set val(ifq) CMUPriQueue           ;# DSR works only with this queue
$ ns wrong.tcl DSR > /dev/null 2>&1; echo "exit status $?"
bash:    20 Segmentation fault      (core dumped) ns wrong.tcl DSR > /dev/null 2>&1
exit status 139
$ tail -1 dsr.tr | cut -c1-40
s 17.000000000 _0_ AGT  --- 68
$ rm -f core

It ran normally for seventeen seconds and then stopped dead, with exit status 139: a segmentation fault, the program touching memory it does not own. The trace ends at 17.0 s. The program died a little later, at the first link break (17.54 s, as the correct run below shows), and the trace lines it had not yet written to the file died with it. (On some systems a file called core also appears, the crashed program's memory, about 20 MB; the last command deletes it. The number after bash: is a process number and differs every time.) The cause is in DSR's source. It is handed its queue as a general object and turns it into a CMUPriQueue without checking (dsragent.cc, line 574); when a link breaks it calls a method that only a CMUPriQueue has (line 2710), to pull out the packets waiting for the dead link. With any other queue that call jumps into nothing. So -ifqType CMUPriQueue is not a preference for DSR but a requirement, and the failure appears only when a link first breaks: a DSR run with no breaks would seem fine.

munotes.in149

Practical 13: The DSR Routing Protocol and Its Overhead against AODV

Step 3: run both, and meet DSR's own trace lines

$ ns compare.tcl AODV > /dev/null
INITIALIZE THE LIST xListHead
SORTING LISTS ...DONE!
$ ns compare.tcl DSR > /dev/null
INITIALIZE THE LIST xListHead
SORTING LISTS ...DONE!
$ grep "^Sconfig" dsr.tr
Sconfig 0.00000 tap: on snoop: rts? on errs? on
Sconfig 0.00000 salvage: on !bd replies? on
Sconfig 0.00000 grat error: on grat reply: on
Sconfig 0.00000 $reply for props: on ring 0 search: on
Sconfig 0.00000 using MOBICACHE

DSR writes lines of its own into the trace, beginning with S. The Sconfig lines at time 0 list how it is configured, and every switch is on: listening to packets not addressed to it (tap), learning routes from them (snoop), salvaging, gratuitous errors and replies (sent without being asked, when a node learns something another needs), answering requests from the cache, and the "ring 0" search. MOBICACHE is the route cache it uses. Other S lines follow flows and failures; an awk script must skip them, which the ones below do by testing the fields they need.

Step 4: DSR's control packets

dsrctl.awk names the parts of each DSR control packet. A DSR packet's trace line ends with four groups (format_dsr in trace/cmu-trace.cc): the number of addresses in its source route; [request flag, request id]; [reply flag, request id, route length, first->last node]; [error flag, count, reporting node, broken link from->to]. One packet can carry more than one of them.

# dsrctl.awk: every DSR control packet sent or forwarded, with its parts named
function node(f) { return substr(f, 2, length(f) - 2) }
function inner(f) { gsub(/[][]/, "", f); return f }
$4 == "RTR" && $7 == "DSR" && ($1 == "s" || $1 == "f") {
    what = ""
    if ($19 == "[1") what = what sprintf("REQUEST %s ", inner($20))
    if ($21 == "[1") what = what sprintf("REPLY for request %s, route %s of %s nodes ", $22, inner($24), $23)
    if ($25 == "[1") what = what sprintf("ERROR link %s broken ", inner($28))
    printf "%10.6f  node %s  %s  %3d bytes  %s\n", $2, node($3), $1 == "s" ? "sends   " : "forwards", $8, what
}
munotes.in150

Practical 13: The DSR Routing Protocol and Its Overhead against AODV

$ awk -f dsrctl.awk dsr.tr
  1.006371  node 0  sends      32 bytes  REQUEST 1
  1.051875  node 0  sends      32 bytes  REQUEST 2
  1.053481  node 1  forwards   48 bytes  REQUEST 2
  1.062227  node 2  forwards   68 bytes  REQUEST 2
  1.065435  node 5  sends      60 bytes  REPLY for request 2, route 0->5 of 4 nodes
  1.070470  node 2  forwards   60 bytes  REPLY for request 2, route 0->5 of 4 nodes
  1.075470  node 1  forwards   60 bytes  REPLY for request 2, route 0->5 of 4 nodes
 17.539947  node 1  sends      44 bytes  ERROR link 1->2 broken
 17.550957  node 1  sends      32 bytes  REQUEST 1
 17.756013  node 0  sends      48 bytes  REQUEST 3 ERROR link 1->2 broken
 17.801886  node 0  sends      48 bytes  REQUEST 4 ERROR link 1->2 broken
 17.810281  node 1  forwards   80 bytes  REQUEST 4 ERROR link 1->2 broken
 19.758311  node 0  sends      32 bytes  REQUEST 5
 19.794420  node 0  sends      32 bytes  REQUEST 6
 19.805096  node 1  forwards   48 bytes  REQUEST 6
 27.755760  node 0  sends      32 bytes  REQUEST 7
 27.806361  node 0  sends      32 bytes  REQUEST 8
 27.816350  node 1  forwards   48 bytes  REQUEST 8
 27.825926  node 4  forwards   68 bytes  REQUEST 8
 27.834143  node 5  sends      60 bytes  REPLY for request 8, route 0->5 of 4 nodes
 27.839479  node 4  forwards   60 bytes  REPLY for request 8, route 0->5 of 4 nodes
 27.844519  node 1  forwards   60 bytes  REPLY for request 8, route 0->5 of 4 nodes
 37.534813  node 0  sends      32 bytes  REQUEST 9
 37.603411  node 0  sends      32 bytes  REQUEST 10
 37.613436  node 3  forwards   48 bytes  REQUEST 10
 37.614505  node 4  sends      56 bytes  REPLY for request 10, route 0->5 of 4 nodes
 37.619344  node 3  forwards   56 bytes  REPLY for request 10, route 0->5 of 4 nodes
 49.060028  node 4  sends      48 bytes  ERROR link 4->5 broken
 49.070374  node 3  forwards   48 bytes  ERROR link 4->5 broken
 49.104500  node 4  sends      32 bytes  REQUEST 1
 49.105401  node 2  sends      48 bytes  REPLY for request 1, route 4->5 of 3 nodes
 49.251731  node 0  sends      48 bytes  REQUEST 11 ERROR link 4->5 broken
 49.252699  node 3  sends      56 bytes  REPLY for request 11, route 0->5 of 5 nodes
munotes.in151

Practical 13: The DSR Routing Protocol and Its Overhead against AODV

Read it against AODV's list from Practical 12.

Discovery starts with a question to the neighbours. At 1.006 s node 0 sends request 1, which goes one hop only: the ring 0 search, asking whether any neighbour already has a route in its cache. None does, and 45 ms later node 0 sends request 2, which propagates.

The request grows as it records the route. Request 2 is 32 bytes from node 0, 48 from node 1 and 68 from node 2: each node adds its address. Node 5's reply, 60 bytes, carries the whole route, 0-1-2-5, four nodes, back to the source.

The first break: an error, a failed salvage, and a search that backs off. At 17.54 s node 1 cannot reach node 2. It sends node 0 an ERROR naming the link 1->2, and tries to salvage the packet it holds with a ring 0 request of its own at 17.55 s; no neighbour of node 1 has another route, and the packet is lost. Node 0's next two requests carry the error with them (the ERROR on the 17.756 s and 17.802 s lines), so that every node that hears the search also forgets the dead link. Then node 0 waits: its requests go at 17.8, 19.8 and 27.8 s, each a ring 0 request and a propagating one. The waits are DSR's back-off (dsragent.cc, line 325): 2^(2 × n) × 0.5 s after the n-th unanswered request, never more than 10 s; 2 s after the first, 8 s after the second. The request at 27.8 s finds the route 0-1-4-5 at once.

The partition's packets were kept. DSR holds packets waiting for a route in a send buffer of 64 packets for up to 30 s (SEND_BUF_SIZE and SEND_TIMEOUT, dsragent.h). The forty packets sent during the ten seconds without a route all waited there and went at 27.8 s. AODV, in Practical 12, dropped 27 of them when its search gave up.

The break at the source: answered from a cache, nothing lost. At 37.5 s node 0's own packet fails on the link to node 1. DSR keeps it, sends its ring 0 request at 37.53 s and a propagating one at 37.60 s, and the answer comes not from node 5 but from node 4's cache: node 4 already knew a route to 5, and replied with the whole route 0-3-4-5. The failed packet was sent again on that route and arrived at 37.64 s.

The break next to the destination: salvaged from a neighbour's cache. At 49.06 s node 4 loses node 5, sends an ERROR back towards the source, and asks its neighbours; node 2 answers from its cache with the route 4-2-5, and node 4 salvages the packet it holds along it. When node 0 then asks, node 3 answers from its cache with the five-node route 0-3-4-2-5. Neither request travelled further than one hop.

munotes.in152

Practical 13: The DSR Routing Protocol and Its Overhead against AODV

Step 5: the data packets, and the flow state

route.awk, from Practical 11, shows where DSR's data went:

# route.awk: the route every data packet took, grouped into runs of packets that went the same way
$7 == "cbr" && $1 == "s" && $4 == "AGT" { t[$6] = $2; path[$6] = "0"; ids[++n] = $6 }
$7 == "cbr" && $1 == "f" && $4 == "RTR" { path[$6] = path[$6] "-" substr($3, 2, length($3) - 2) }
$7 == "cbr" && $1 == "r" && $4 == "AGT" { got[$6] = 1; path[$6] = path[$6] "-5" }
$7 == "cbr" && $1 == "D" { why[$6] = $5 }
END {
    for (i = 1; i <= n; i++) {
        id = ids[i]
        how = (id in got) ? "delivered via " path[id] : "LOST (" why[id] ") at node " substr(path[id], length(path[id]))
        if (how != last) {
            if (i > 1) printf "%6.2f to %6.2f s  %3d packets  %s\n", first, t[ids[i - 1]], count, last
            first = t[id]; count = 0; last = how
        }
        count++
    }
    printf "%6.2f to %6.2f s  %3d packets  %s\n", first, t[ids[n]], count, last
}
$ awk -f route.awk dsr.tr
  1.00 to  17.25 s   66 packets  delivered via 0-1-2-5
 17.50 to  17.50 s    1 packets  LOST (NRTE) at node 1
 17.75 to  37.25 s   79 packets  delivered via 0-1-4-5
 37.50 to  48.75 s   46 packets  delivered via 0-3-4-5
 49.00 to  49.00 s    1 packets  delivered via 0-3-4-4-2-5
 49.25 to  58.75 s   39 packets  delivered via 0-3-4-2-5

One packet lost in sixty seconds: the one node 1 tried and failed to salvage.

A source route should make every DSR data packet bigger than AODV's. Count the sizes of the data packets actually sent on the air:

$ awk '$4 == "RTR" && ($1 == "s" || $1 == "f") && $7 == "cbr" {print $8}' dsr.tr | sort | uniq -c
    674 532
     45 556
     18 560

Most are 532 bytes, exactly AODV's size. ns-2.35's DSR has the flow state extension built in and switched on (dsragent_enable_flowstate = true, dsragent.cc line 110). The first packets of a flow carry the full route and set up state along it (the 556- and 560-byte packets, logged as SFESTs); after that, packets of the flow carry only a small flow identifier, and while a flow is the default for its source and destination, not even that. The route travels once, not in every packet.

munotes.in153

Practical 13: The DSR Routing Protocol and Its Overhead against AODV

Step 6: the overhead, side by side

overhead.awk measures both runs the same way:

# overhead.awk: what routing cost and what it delivered, from an old-format wireless trace
$4 == "RTR" && ($1 == "s" || $1 == "f") && ($7 == "AODV" || $7 == "DSR") { ctl++; ctlbytes += $8 }
$4 == "RTR" && ($1 == "s" || $1 == "f") && $7 == "cbr" { tx++; txbytes += $8 }
$1 == "s" && $4 == "AGT" && $7 == "cbr" { sent++; start[$6] = $2 }
$1 == "r" && $4 == "AGT" && $7 == "cbr" { got++; delay += $2 - start[$6] }
END {
    printf "routing packets sent        %d\n", ctl
    printf "routing bytes sent          %d\n", ctlbytes
    printf "data packets delivered      %d of %d\n", got, sent
    printf "routing packets per delivered data packet  %.3f\n", ctl / got
    printf "data transmissions          %d, %.1f bytes each on average\n", tx, txbytes / tx
    printf "average delay               %.1f ms\n", delay / got * 1000
}
$ awk -f overhead.awk aodv.tr
routing packets sent        37
routing bytes sent          1696
data packets delivered      203 of 232
routing packets per delivered data packet  0.182
data transmissions          653, 532.0 bytes each on average
average delay               1061.7 ms
$ awk -f overhead.awk dsr.tr
routing packets sent        33
routing bytes sent          1588
data packets delivered      231 of 232
routing packets per delivered data packet  0.143
data transmissions          737, 534.1 bytes each on average
average delay               1031.9 ms

On this network, with these moves and this traffic, DSR did better on every count. It sent four fewer routing packets and 108 fewer routing bytes, and delivered 28 more data packets, so each delivered packet cost 0.143 routing packets against AODV's 0.182. It made 84 more data transmissions, 737 - 653 = 84 = 28 × 3: exactly the 28 extra packets it delivered, three hops each. Its source routes added 2.1 bytes to the average data packet. The average delays are both about a second, because both include packets that waited out the partition; DSR's includes more of them, since it delivered them instead of dropping them.

Why, here. Every advantage came from a mechanism step 4 showed: the send buffer and a back-off that asked again at 27.8 s where AODV, in its hold-down, waited until 34.25 s; route caches that let nodes 4, 2 and 3 answer without the request reaching the destination; and a failed packet at the source kept and sent again rather than dropped. What this does not show is that DSR is better in general. This is one small network with one flow and a few planned moves. Cached routes that are old can be wrong, and a network with more nodes, more flows and more movement tests that; Practical 14 measures AODV, DSR and DSDV on such a network.

munotes.in154

Practical 13: The DSR Routing Protocol and Its Overhead against AODV

Procedure

  1. Write compare.tcl, which takes the routing protocol from the command line and gives DSR its CMUPriQueue.
  2. Run DSR with the default queue, and find where and why it crashes.
  3. Run the script with AODV and with DSR, and read DSR's configuration lines.
  4. Decode DSR's control packets with dsrctl.awk, and follow its discovery, errors, back-off, cache replies and salvaging.
  5. Group DSR's data packets by route, and count the sizes of the data packets on the air.
  6. Measure both runs with overhead.awk, and explain every difference.

Observations

MeasuredAODVDSR
Routing packets sent3733
Routing bytes sent16961588
Data packets delivered203 of 232231 of 232
Routing packets per delivered data packet0.1820.143
Data transmissions653737
Average data packet on the air532.0 bytes534.1 bytes
Average delay1061.7 ms1031.9 ms
Lost at the first break1 in flight, 27 dropped by the source1, a salvage that failed
Lost at the break at the source10
Replies not from the destination1, stale, ignored3, from caches, all used
Longest wait between requests after the break10 s hold-down (22.00 to 34.25 s, via the give-up at 24.25 s)8 s back-off (19.8 to 27.8 s)

Result

DSR was simulated in NS-2 on Practical 12's network, movements and traffic, from the same script as AODV. It needs CMUPriQueue as its interface queue: with the default queue it crashes at the first link break. DSR discovered routes with requests that recorded the path, first one hop out (the ring 0 search) and then further; it answered three requests from route caches, salvaged a packet at a broken link, and piggybacked route errors on its requests. On this scenario DSR sent 33 routing packets (1588 bytes) against AODV's 37 (1696 bytes), delivered 231 of 232 data packets against 203, and so had a normalized routing load of 0.143 against 0.182. Because ns-2.35's DSR sends a route once per flow and then a flow identifier, its source routes added only 2.1 bytes to the average data packet. The result holds for this one scenario; it is not a general ranking of the two protocols.

Where marks are lost

Running DSR with the default queue. It crashes at the first link break, and a run without breaks looks fine. Use CMUPriQueue, and say why.

Treating DSR's S lines as packets. Lines beginning Sconfig, SFs, SSendFailure and the like are DSR's log. They have different fields; test the fields you need, as these scripts do.

munotes.in155

Practical 13: The DSR Routing Protocol and Its Overhead against AODV

Charging DSR a full route in every data packet. In ns-2.35 the flow state sends the route once per flow. Measure the sizes before writing about them.

Comparing protocols on different runs. Two protocols on two different scenarios, or two different setdest files, compare the scenarios. Change only the protocol.

Counting only requests as overhead. Replies, errors and every forwarded copy are routing packets too.

Declaring a winner from one scenario. Say what the numbers show for this network, and what would have to be measured to say more.

For the journal

Write: aim; what DSR is and how it differs from AODV, in five lines; the four measures of overhead; compare.tcl; the crash with the default queue and its cause; DSR's configuration lines; the list of DSR control packets, read phase by phase; the route groups and the data packet sizes, with the flow state; the two overhead.awk outputs and the table; the reasons for each difference; observations; result.

Quick revision

  • DSR: on demand, source routing: the whole route is in the packet.
  • A request records the route as it travels; the reply returns the whole route.
  • The route cache holds many routes, learned from replies, forwarded packets and overheard ones (tap).
  • Ring 0 search: first ask the neighbours only, then the network.
  • A broken link: a route ERROR to the source; the node holding the packet may salvage it from its cache.
  • Errors ride on the next request, so every node that hears the search forgets the dead link.
  • Back-off: 2^(2 × n) × 0.5 s after n unanswered requests, at most 10 s.
  • Send buffer: 64 packets, kept up to 30 s.
  • In ns-2: -ifqType CMUPriQueue, or a crash at the first break.
  • ns-2.35 flow state: the route goes once per flow, then a flow id.
  • Normalized routing load = routing packets ÷ delivered data packets.

Questions you must be able to answer

1. What is source routing? The source puts the complete route, every node in order, in the packet's header, and each node passes the packet to the next address on that list. Intermediate nodes need no routing table entry for the packet's destination.

2. How does a DSR route request differ from an AODV one? It records the route: each node that passes it on adds its own address. When it reaches the destination it carries the whole path, and the reply returns that path to the source.

3. What is DSR's route cache, and how is it filled? The routes a node knows, several per destination if it has them. It is filled from replies to its own requests, from the source routes in packets it forwards and, with the tap on, from packets it overhears.

munotes.in156

Practical 13: The DSR Routing Protocol and Its Overhead against AODV

4. What is the ring 0 search? A first request that goes only to the node's neighbours, to see whether one of them has a route in its cache, before a request is sent across the network.

5. What does DSR do when a link on a route breaks? The node that finds the break sends a route ERROR to the source naming the broken link, and nodes that hear it drop the routes that use the link. The node holding the packet tries to salvage it by another route from its cache.

6. At the first break, DSR lost one packet and AODV twenty-eight. Why? AODV's search gave up after its retries and dropped the 27 packets it was holding, then waited ten seconds. DSR kept them in its send buffer, which holds packets for up to 30 s, backed off more gently, found the new route at 27.8 s, and sent them. Each lost the packet that was in flight at the break.

7. Why must DSR use CMUPriQueue in ns-2? DSR's code treats its interface queue as a CMUPriQueue without checking, and at the first link break calls a function only that queue has, to remove packets waiting for the dead link. With any other queue the simulator crashes there.

8. What is the normalized routing load? What was it for each protocol here? The number of routing packets sent for each data packet delivered. AODV: 37 routing packets for 203 delivered, 0.182 each to three places. DSR: 33 for 231, 0.143 each.

9. Why were most of DSR's data packets 532 bytes, the same as AODV's? Because ns-2.35's DSR uses flow state: the first packets of a flow carry the full route and set the flow up, and later ones carry only a flow identifier, or nothing when the flow is the default one.

10. DSR made 84 more data transmissions than AODV. Why? It delivered 28 more data packets, and each travelled three hops: 28 × 3 = 84.

11. What is a gratuitous route reply? A reply a node sends without being asked by a request, when it learns that a source is using a longer route than one it knows.

12. Does this practical show that DSR is better than AODV? Only for this network: six nodes, one flow and a few planned moves. A general comparison needs networks with more nodes, flows and movement, the same scenarios for every protocol, and several runs.

Contents This chapter on its own page

munotes.in157

Chapter Seventeen

Practical 14: Performance Evaluation of MANET Routing Protocols

Syllabus topic Module 2, "Performance Evaluation of MANET Protocols: Measure throughput, packet delivery ratio, and end-to-end delay for different MANET routing protocols."

Aim

To measure the throughput, packet delivery ratio and average end-to-end delay of three MANET routing protocols, AODV, DSR and DSDV, on the same random mobile networks at four levels of mobility, and to explain the differences from the traces and the protocols' own code.

What you need to know before you start

The three protocols. AODV (Practical 12) and DSR (Practical 13) are reactive, or on-demand: they look for a route only when a packet needs one. DSDV, Destination-Sequenced Distance Vector, is proactive, or table-driven: every node keeps a route to every other node at all times, built from routing tables that every node broadcasts to its neighbours, in full every 15 s or so and in part whenever something changes. A proactive protocol has a route ready the moment a packet arrives, or a stale one, or none.

Three measures, as the syllabus names them, and one more:

MeasureDefinitionWhat it tells you
throughputdata bits delivered to the destinations each secondhow much useful traffic the network carried
packet delivery ratiodata packets delivered ÷ data packets senthow reliable the routing was
average end-to-end delaythe mean, over delivered packets only, of (time received - time sent)how long a delivered packet took
routing loadrouting packets sent ÷ data packets deliveredwhat the routing cost, from Practical 13

Two cautions come with them. Throughput follows the delivery ratio when the traffic offered is fixed, as it is here: five flows of a 512-byte packet every quarter second offer 5 × 512 × 8 × 4 = 81920 bits a second, so a protocol delivering 97 per cent carries about 79 kbit/s. Delay is averaged over delivered packets only, so a protocol that drops packets instead of waiting for a route can show a low delay and a poor delivery ratio at the same time.

A fair evaluation changes one thing at a time and repeats. The same networks, movements and traffic go to every protocol; the level of mobility, here the nodes' top speed, is varied; and each point is measured on several random networks, because a single random network can be unusual. This practical uses three networks per point and reports the average, with the range of the delivery ratio beside it.

Step 1: the evaluation script

eval.tcl takes three arguments: the protocol, the top speed in metres per second, and a seed that picks one random network.

# eval.tcl: twenty nodes moving by random waypoint, five CBR flows, routed by the protocol
# named on the command line, at a chosen top speed, with a chosen random network
#   ns eval.tcl <AODV|DSR|DSDV> <top speed, m/s> <seed>
set val(chan)   Channel/WirelessChannel
set val(prop)   Propagation/TwoRayGround
set val(netif)  Phy/WirelessPhy
set val(mac)    Mac/802_11
set val(ifq)    Queue/DropTail/PriQueue
set val(ll)     LL
set val(ant)    Antenna/OmniAntenna
set val(ifqlen) 50
set val(nn)     20
set val(rp)     [lindex $argv 0]
set val(speed)  [lindex $argv 1]
set val(seed)   [lindex $argv 2]
set val(x)      1000
set val(y)      500
set val(pause)  2.0
set val(stop)   100.0
if {$val(rp) == "DSR"} {
    set val(ifq) CMUPriQueue
}

set ns [new Simulator]
set tf [open run.tr w]
$ns trace-all $tf
set nf [open run.nam w]
$ns namtrace-all-wireless $nf $val(x) $val(y)

set topo [new Topography]
$topo load_flatgrid $val(x) $val(y)
create-god $val(nn)

$ns node-config -adhocRouting $val(rp) -llType $val(ll) -macType $val(mac) \
    -ifqType $val(ifq) -ifqLen $val(ifqlen) -antType $val(ant) \
    -propType $val(prop) -phyType $val(netif) -channel [new $val(chan)] \
    -topoInstance $topo -agentTrace ON -routerTrace ON -macTrace OFF \
    -movementTrace OFF

# random waypoint from seeded generators: the same network for every protocol
set place [new RNG]
$place seed $val(seed)
set move [new RNG]
$move seed [expr $val(seed) + 100]
for {set i 0} {$i < $val(nn)} {incr i} {
    set node($i) [$ns node]
    $node($i) random-motion 0
    set x($i) [$place uniform 0 $val(x)]
    set y($i) [$place uniform 0 $val(y)]
    $node($i) set X_ $x($i)
    $node($i) set Y_ $y($i)
    $node($i) set Z_ 0
    $ns initial_node_pos $node($i) 30
}
for {set i 0} {$i < $val(nn)} {incr i} {
    set t 0.0
    while {$t < $val(stop)} {
        set nx [$move uniform 0 $val(x)]
        set ny [$move uniform 0 $val(y)]
        set v  [$move uniform 1 $val(speed)]
        $ns at $t "$node($i) setdest $nx $ny $v"
        set t [expr {$t + hypot($nx - $x($i), $ny - $y($i)) / $v + $val(pause)}]
        set x($i) $nx
        set y($i) $ny
    }
}

# five flows, a 512-byte packet every quarter second each, from 5 s to 95 s
foreach {src dst} {0 10 2 12 4 14 6 16 8 18} {
    set udp [new Agent/UDP]
    $ns attach-agent $node($src) $udp
    set sink [new Agent/Null]
    $ns attach-agent $node($dst) $sink
    $ns connect $udp $sink
    set cbr [new Application/Traffic/CBR]
    $cbr set packetSize_ 512
    $cbr set interval_ 0.25
    $cbr attach-agent $udp
    $ns at [expr 5.0 + $src * 0.01] "$cbr start"
    $ns at 95.0 "$cbr stop"
}

for {set i 0} {$i < $val(nn)} {incr i} {
    $ns at $val(stop) "$node($i) reset"
}
$ns at $val(stop) "finish"
proc finish {} {
    global ns tf nf
    $ns flush-trace
    close $tf
    close $nf
    exit 0
}
$ns run
munotes.in158

Practical 14: Performance Evaluation of MANET Routing Protocols

Everything except the three arguments is fixed: twenty nodes in 1000 m by 500 m; five flows, 0 to 10, 2 to 12, 4 to 14, 6 to 16 and 8 to 18, from 5 s to 95 s; 100 s in all.

The movement is random waypoint, generated inside the script. Each node repeatedly picks a random point and a random speed between 1 m/s and the top speed, travels there, pauses 2 s, and picks again. Practical 11's setdest does the same, but seeds itself from the clock and so writes a different file every time. Here the random numbers come from ns-2's own generator, RNG, with a fixed seed: the same seed gives the same network on every run, for every protocol. Two generators are used, one for the starting positions and one for the moves, so that a network's starting positions are the same at every speed.

munotes.in159

Practical 14: Performance Evaluation of MANET Routing Protocols

Every run writes run.tr and run.nam, overwriting the last run's, so that 36 runs never need 36 traces on the disk.

Step 2: measuring one run

one.awk reduces a run to one line: protocol, speed, network, delivery ratio in per cent, throughput in kbit/s, average delay in ms, and routing load.

# one.awk: one run's measures on one line: protocol speed seed delivered% throughput delay routing-load
$1 == "s" && $4 == "AGT" && $7 == "cbr" { sent++; start[$6] = $2; size[$6] = $8 }
$1 == "r" && $4 == "AGT" && $7 == "cbr" { got++; bits += size[$6] * 8; delay += $2 - start[$6] }
$4 == "RTR" && ($1 == "s" || $1 == "f") && $7 != "cbr" { routing++ }
END { printf "%s %s %s %.1f %.1f %.1f %.2f\n", proto, speed, seed, got * 100 / sent, bits / 90 / 1000, delay / got * 1000, routing / got }

Throughput divides the delivered bits by 90 s, the time the flows run. Each delivered packet counts at the size its source sent, 512 bytes, because the size a trace line shows on arrival differs between protocols (Practical 13).

$ ns eval.tcl AODV 10 1 > /dev/null 2>&1
$ awk -v proto=AODV -v speed=10 -v seed=1 -f one.awk run.tr
AODV 10 1 92.8 76.0 140.5 0.84

Step 3: thirty-six runs

A short shell script runs every protocol, at every speed, on every network, and prints one line per run:

# sweep.sh: every protocol, at every top speed, on every network, one line of measures per run
for v in 2 5 10 20; do
    for p in AODV DSR DSDV; do
        for s in 1 2 3; do
            ns eval.tcl $p $v $s > /dev/null 2>&1
            awk -v proto=$p -v speed=$v -v seed=$s -f one.awk run.tr
        done
    done
done

On the machine this book was checked on, all 36 runs took under half a minute. Keep the output: it is the experiment's data.

$ bash sweep.sh > results.txt
$ wc -l results.txt
36 results.txt

summary.awk averages the three networks at each point, and gives the lowest and highest delivery ratio among them:

munotes.in160

Practical 14: Performance Evaluation of MANET Routing Protocols

# summary.awk: per protocol and speed, the average of every measure and the range of the delivery ratio
{ k = $1 " " $2; n[k]++; order[k] = NR
  pdr[k] += $4; tput[k] += $5; dly[k] += $6; rl[k] += $7
  if (!(k in lo) || $4 < lo[k]) lo[k] = $4
  if (!(k in hi) || $4 > hi[k]) hi[k] = $4 }
END {
    printf "%-5s %5s  %13s  %18s  %13s  %12s\n", "", "speed", "throughput", "delivered", "delay", "routing load"
    for (k in n) line[order[k]] = k
    for (i = 1; i <= NR; i++) if (i in line) {
        k = line[i]; split(k, f, " ")
        printf "%-5s %3s m/s  %7.1f kbit/s  %5.1f %% (%5.1f-%5.1f)  %8.1f ms  %12.2f\n",
            f[1], f[2], tput[k] / n[k], pdr[k] / n[k], lo[k], hi[k], dly[k] / n[k], rl[k] / n[k]
    }
}
$ awk -f summary.awk results.txt
      speed     throughput           delivered          delay  routing load
AODV    2 m/s     75.0 kbit/s   91.5 % ( 79.8- 99.3)     122.3 ms          0.20
DSR     2 m/s     76.1 kbit/s   92.9 % ( 79.9- 99.7)     174.5 ms          0.13
DSDV    2 m/s     46.6 kbit/s   56.9 % ( 37.4- 82.8)      14.3 ms          0.36
AODV    5 m/s     79.4 kbit/s   97.0 % ( 92.9- 99.8)      91.2 ms          0.22
DSR     5 m/s     81.4 kbit/s   99.4 % ( 98.3-100.0)     126.5 ms          0.12
DSDV    5 m/s     45.9 kbit/s   56.1 % ( 42.6- 65.4)      11.7 ms          0.35
AODV   10 m/s     79.4 kbit/s   96.9 % ( 92.8- 99.3)      65.2 ms          0.52
DSR    10 m/s     81.3 kbit/s   99.2 % ( 98.0-100.0)     151.5 ms          0.27
DSDV   10 m/s     38.6 kbit/s   47.1 % ( 20.3- 69.3)      12.7 ms          0.54
AODV   20 m/s     79.3 kbit/s   96.9 % ( 95.9- 97.6)     195.4 ms          0.86
DSR    20 m/s     80.5 kbit/s   98.3 % ( 96.3- 99.7)     124.7 ms          0.46
DSDV   20 m/s     31.2 kbit/s   38.1 % ( 35.6- 42.4)      10.4 ms          0.47
Two charts sharing one axis, top speed of the nodes, 2 to 20 metres a second. Upper chart, packet delivery ratio in per cent: AODV and DSR close together between 91 and 99 per cent, both a little lower at 2 metres a second, under a faint line for the time the flows were connected, 92 to 99 per cent; DSDV far below, falling from 57 to 38 per cent. Lower chart, average end-to-end delay in milliseconds: DSDV flat at 10 to 14; AODV and DSR between 65 and 195, moving up and down with speed.

Figure 17.1 Delivery ratio and delay against top speed, each point the average of three networks

Step 4: how connected were the networks?

Before comparing protocols, ask what the network allowed. If a flow's two ends are not joined by any chain of links, no routing protocol can deliver its packets at that moment. reach.py reads the node positions from the NAM file, as Practical 11's frames.py does, and checks every half second whether each flow's ends are joined:

# reach.py: for how much of the traffic's time each flow's two ends were joined by a chain
# of links within 250 m. A packet sent while they are apart can still arrive, later, if the
# routing protocol keeps it until they are joined.
import math
import sys

RANGE, START, STOP, STEP = 250.0, 5.0, 95.0, 0.5
FLOWS = [(0, 10), (2, 12), (4, 14), (6, 16), (8, 18)]

start, moves = {}, {}
for line in open(sys.argv[1]):
    if not line.startswith('n -t '):
        continue
    f = line.split()
    o = dict(zip(f[1::2], f[2::2]))
    n = int(o['-s'])
    if o['-t'] == '*':
        start[n] = (float(o['-x']), float(o['-y']))
    else:
        moves.setdefault(n, []).append(tuple(float(o[k]) for k in ('-t', '-x', '-y', '-U', '-V', '-T')))


def position(n, t):
    x, y = start[n]
    for t0, x0, y0, u, v, dur in moves.get(n, []):
        if t0 > t:
            break
        x, y = x0 + u * min(t - t0, dur), y0 + v * min(t - t0, dur)
    return x, y


def reachable(pos, a):
    seen, todo = {a}, [a]
    while todo:
        m = todo.pop()
        for k in pos:
            if k not in seen and math.dist(pos[m], pos[k]) <= RANGE:
                seen.add(k)
                todo.append(k)
    return seen


if len(start) < 2:
    sys.exit('no starting positions in %s: does the script call initial_node_pos?' % sys.argv[1])
steps = int((STOP - START) / STEP)
joined = {fl: 0 for fl in FLOWS}
for i in range(steps):
    t = START + i * STEP
    pos = {n: position(n, t) for n in start}
    for a, b in FLOWS:
        if b in reachable(pos, a):
            joined[(a, b)] += 1
for (a, b), k in joined.items():
    print('flow %d to %2d: ends joined %5.1f %% of the time' % (a, b, 100.0 * k / steps))
print('time connected, all five flows: %.1f %%' % (100.0 * sum(joined.values()) / (steps * len(FLOWS))))
munotes.in161

Practical 14: Performance Evaluation of MANET Routing Protocols

The network whose delivery ratios were lowest at 2 m/s, network 2:

$ ns eval.tcl DSDV 2 2 > /dev/null 2>&1
$ python3 reach.py run.nam
flow 0 to 10: ends joined   0.0 % of the time
flow 2 to 12: ends joined 100.0 % of the time
flow 4 to 14: ends joined 100.0 % of the time
flow 6 to 16: ends joined 100.0 % of the time
flow 8 to 18: ends joined 100.0 % of the time
time connected, all five flows: 80.0 %

And all twelve networks, one line each. The protocol does not matter here, because the movement is the same whichever protocol runs:

# connected.sh: how connected each of the twelve networks was
for v in 2 5 10 20; do
    for s in 1 2 3; do
        ns eval.tcl DSDV $v $s > /dev/null 2>&1
        echo "$v m/s, network $s: $(python3 reach.py run.nam | tail -1)"
    done
done
$ bash connected.sh
2 m/s, network 1: time connected, all five flows: 100.0 %
2 m/s, network 2: time connected, all five flows: 80.0 %
2 m/s, network 3: time connected, all five flows: 96.0 %
5 m/s, network 1: time connected, all five flows: 100.0 %
5 m/s, network 2: time connected, all five flows: 95.9 %
5 m/s, network 3: time connected, all five flows: 100.0 %
10 m/s, network 1: time connected, all five flows: 95.9 %
10 m/s, network 2: time connected, all five flows: 100.0 %
10 m/s, network 3: time connected, all five flows: 100.0 %
20 m/s, network 1: time connected, all five flows: 98.6 %
20 m/s, network 2: time connected, all five flows: 100.0 %
20 m/s, network 3: time connected, all five flows: 96.6 %
munotes.in162

Practical 14: Performance Evaluation of MANET Routing Protocols

What the connectivity explains. At 2 m/s, network 2 kept nodes 0 and 10 apart for the whole run: no chain of links ever joined them, so no protocol could deliver a single packet of that flow, and 80 per cent was the most anyone could deliver on that network. AODV delivered 79.8 per cent there and DSR 79.9. Averaged, the three networks at 2 m/s were connected for (100 + 80.0 + 96.0) / 3 = 92.0 per cent of the time, against about 98.5 per cent at every other speed. Slow nodes stay near where they started, and a pair that starts apart stays apart; faster nodes mix. That, not the routing, is why AODV and DSR delivered less at 2 m/s than at 5.

The time connected is not a strict limit. On network 3 at 2 m/s the flows were joined 96.0 per cent of the time, and DSR delivered 99.1 per cent: it keeps a packet for up to 30 s (Practical 13), and a packet sent while its ends were apart arrived after they met.

Step 5: why each protocol lost what it lost

The trace gives every lost packet a reason. The three most common, for each protocol on network 1 at 10 m/s:

# losses.sh: the three commonest reasons each protocol lost packets, on network 1 at 10 m/s
for p in AODV DSR DSDV; do
    ns eval.tcl $p 10 1 > /dev/null 2>&1
    echo "$p: $(awk '$1 == "D" && $7 == "cbr" {print $4 "/" $5}' run.tr | sort | uniq -c | sort -rn | head -3 | xargs)"
done
$ bash losses.sh
AODV: 68 RTR/CBK 59 RTR/NRTE 3 IFQ/ARP
DSR: 17 RTR/TOUT 17 IFQ/ARP 3 RTR/NRTE
DSDV: 770 RTR/CBK 402 RTR/IFQ 257 IFQ/ARP

AODV lost packets in flight when links broke (CBK) and packets it had held when a search gave up (NRTE): the two mechanisms of Practical 12.

DSR lost few, and two kinds: packets that waited longer than its send buffer's 30 s (TOUT, "packet expired"), and packets for a next hop that had moved away before its address could be resolved (ARP).

DSDV lost many more, and its own code says why:

  1. It is not told when a link breaks. ns-2 sets Agent/DSDV set use_mac_ 0. When the MAC reports that a packet could not reach the next hop, DSDV drops the packet (CBK) and leaves the route in its table (dsdv.cc, line 348), so the next packets for that destination go the same dead way until a routing update replaces it. That is the 770.
  2. With no valid route, it keeps only five packets. DSDV holds up to MAX_QUEUE_LENGTH, 5, packets per destination while it has no route, and throws away the oldest beyond that (dsdv.h, line 64; dsdv.cc, line 936): the 402 IFQ drops. A flow of four packets a second fills five places in just over a second.
  3. Its routes change slowly. A node sends its whole table every 11.25 to 15 s (perup_, 15 s, jittered), and holds back an improved route for twice its settling time, 12 s at first, before advertising it (dsdv.cc, line 703), so that a route that is about to change again is not advertised. In a network whose links change every few seconds, much of the table is out of date much of the time.
munotes.in163

Practical 14: Performance Evaluation of MANET Routing Protocols

The delay figures follow from the same code. DSDV never waits for a route: a packet either goes at once or is dropped, so the packets that arrive are fast, 10 to 14 ms. AODV and DSR hold packets while they search, so their averages include packets that waited seconds; a few long waits move the average a lot, which is why it jumps between speeds.

Procedure

  1. Write eval.tcl: twenty nodes, random waypoint from seeded generators, five CBR flows, and the protocol, top speed and network taken from the command line.
  2. Reduce one run to one line of measures with one.awk, and check it on one run.
  3. Run all three protocols at four top speeds on three networks each, 36 runs, into results.txt.
  4. Average the three networks at each point with summary.awk, with the range of the delivery ratio.
  5. Measure how long each flow's two ends were connected in every network with reach.py.
  6. Explain the differences: the drop reasons of each protocol, from the trace and the protocols' code.

Observations

Measured, at 2, 5, 10 and 20 m/sAODVDSRDSDV
Packet delivery ratio, per cent91.5, 97.0, 96.9, 96.992.9, 99.4, 99.2, 98.356.9, 56.1, 47.1, 38.1
Throughput, kbit/s (81.9 offered)75.0, 79.4, 79.4, 79.376.1, 81.4, 81.3, 80.546.6, 45.9, 38.6, 31.2
Average end-to-end delay, ms122.3, 91.2, 65.2, 195.4174.5, 126.5, 151.5, 124.714.3, 11.7, 12.7, 10.4
Routing load, per delivered packet0.20, 0.22, 0.52, 0.860.13, 0.12, 0.27, 0.460.36, 0.35, 0.54, 0.47
munotes.in164

Practical 14: Performance Evaluation of MANET Routing Protocols

Networks, at 2, 5, 10 and 20 m/sTime connected, all five flows, average of three
The same for every protocol92.0, 98.6, 98.6, 98.4 per cent

Result

AODV, DSR and DSDV were each run on the same twelve random networks, three at each of four top speeds, with five CBR flows offering 81.9 kbit/s. The two on-demand protocols delivered 91.5 to 99.4 per cent of the data on average, DSR a little more than AODV at every speed and at a lower routing load; DSDV delivered 38.1 to 56.9 per cent, less as the nodes moved faster. Throughput followed the delivery ratio, as it must when the offered load is fixed. DSDV had the lowest average delay, 10 to 14 ms, because it drops packets it cannot route at once, while AODV and DSR averaged 65 to 195 ms including packets that waited for a route. The lower delivery of every protocol at 2 m/s was the networks': one of them kept a flow's two ends apart for the whole run. DSDV's losses were traced to three things in ns-2's implementation: it ignores link failures reported by the MAC (use_mac_ 0), holds only five packets for a destination without a route, and advertises changed routes slowly.

Where marks are lost

Comparing protocols on different networks. Each protocol must see the same positions, movements and traffic. Three runs of setdest give three different networks; a seeded generator, or one saved setdest file, gives the same one.

One run per point. At 10 m/s DSDV delivered 20.3 per cent on one network and 69.3 on another. A single run can be the unusual one; report the average and the spread.

Reporting delay without delivery. DSDV's delay is the best because it delivers the fewest packets and drops the rest at once. Delay is averaged over delivered packets only; always give the delivery ratio beside it.

Treating throughput and delivery ratio as two findings. With a fixed offered load, throughput is the delivery ratio times that load. They are one finding.

Blaming a protocol for a partition. When no chain of links joins a flow's ends, no protocol can deliver. Measure the connectivity before comparing.

Counting a delivered packet at the size its trace line shows. Protocols differ in what the size includes on arrival. Count the size the source sent.

Running ns-2.35 on an ARM computer. On some of these networks the packaged ns stops with Floating point exception inside AODV; on Intel and AMD computers it does not. This book's results come from a copy with that one fault corrected, and match an Intel computer's exactly.

For the journal

Write: aim; the three protocols, reactive and proactive, in three lines each; the table of measures with their definitions; eval.tcl and how its seeded random waypoint works; one.awk and summary.awk; the 36-run loop and the summary; the figure, drawn by hand from the summary if you have no plotting program; the connectivity of each network from reach.py, and what it explains; the drop reasons of each protocol, with DSDV's three causes; observations; result.

munotes.in165

Practical 14: Performance Evaluation of MANET Routing Protocols

Quick revision

  • Reactive (on-demand): AODV, DSR. Proactive (table-driven): DSDV.
  • Throughput = data bits delivered per second; packet delivery ratio = delivered ÷ sent; average end-to-end delay = mean (received - sent) over delivered packets.
  • Offered load here: 5 × 512 × 8 × 4 = 81920 bit/s; throughput follows the delivery ratio.
  • Same networks for every protocol; several networks per point; report average and spread.
  • ns-2's RNG with a fixed seed repeats exactly; setdest does not.
  • Check connectivity: a partition defeats every protocol.
  • AODV and DSR: 91.5 to 99.4 per cent delivered; DSDV: 38.1 to 56.9, falling with speed.
  • DSDV in ns-2: use_mac_ 0 (link failures ignored), 5 packets held per destination, updates every 11.25 to 15 s, changed routes held back.
  • Delay: DSDV lowest (no waiting); AODV and DSR higher (packets wait for route discovery).
  • Routing load rises with speed for AODV and DSR: more breaks, more searches.

Questions you must be able to answer

1. Define throughput, packet delivery ratio and average end-to-end delay. Throughput is the number of data bits delivered to the destinations per second. The packet delivery ratio is the number of data packets delivered divided by the number sent. The average end-to-end delay is the mean, over the delivered packets, of the time each took from being sent by the source's application to being received by the destination's.

2. Why can a protocol with the lowest delay be the worst protocol? Because delay is averaged only over the packets that arrive. DSDV sends a packet at once or drops it, so the few that arrive are fast, while it loses half of them. The delivery ratio must always be read beside the delay.

3. Why must every protocol be run on the same networks? Because the network itself, where the nodes are and how they move, changes the results as much as the protocol does. Only if everything else is the same can a difference be put down to the protocol.

4. Why three networks at each speed, not one? Because one random network can be unusual. At 2 m/s one network kept a flow's two ends apart for the whole run, and on its own it would have made every protocol look poor at low speed.

5. What does eval.tcl do that the setdest generator does not? It generates the random waypoint movement from ns-2's random number generator with a fixed seed, so the same seed gives exactly the same network on every run and for every protocol. setdest seeds itself from the clock and writes a different file every time.

munotes.in166

Practical 14: Performance Evaluation of MANET Routing Protocols

6. Why did AODV and DSR deliver less at 2 m/s than at 5 m/s? Because the 2 m/s networks were less connected, 92.0 per cent of the time on average against about 98.5 per cent at the other speeds. In one of them nodes 0 and 10 were never joined, and slow nodes do not move far enough to meet.

7. Give three reasons, from its code, why ns-2's DSDV delivered so little. It ignores link failures reported by the MAC (use_mac_ 0), so it keeps sending into broken links; with no valid route it keeps only five packets per destination and drops the rest; and it sends full updates only every 11.25 to 15 s and holds back changed routes for twice their settling time, so its table is often out of date.

8. Why does the routing load of AODV and DSR rise with speed? Faster nodes break more links, and every break costs route errors and new route requests and replies. More routing packets are spent for each data packet delivered.

9. Five flows each send a 512-byte packet four times a second. What is the offered load, and what throughput does 97 per cent delivery give? 5 × 512 × 8 × 4 = 81920 bits a second, 81.9 kbit/s. Delivering 97 per cent of it is about 79.5 kbit/s.

10. On this evidence, is a reactive or a proactive protocol better for a MANET whose nodes keep moving? For these networks, the reactive protocols: they delivered 91.5 to 99.4 per cent against DSDV's 38.1 to 56.9, and DSDV fell further as speed rose. The evidence is ns-2's implementations with their default settings, on twenty nodes and five flows.

11. What does reach.py measure, and why is it not a strict upper limit on delivery? For how much of the time each flow's two ends were joined by some chain of links within 250 m. It is not a strict limit because a protocol that keeps packets, like DSR, can deliver a packet sent while the ends were apart once they are joined again.

12. What would make this evaluation stronger? More networks at each point, with the spread reported as a confidence interval; more nodes, flows and speeds; longer runs; and other kinds of traffic, such as TCP.

Contents This chapter on its own page

munotes.in167

Chapter Eighteen

Practical 15: The CSMA/CA MAC Protocol

Syllabus topic Module 2, "Simulation of CSMA/CA MAC Protocol: Implement CSMA/CA protocol simulation and evaluate collision avoidance mechanisms."

Aim

To implement a simulation of CSMA/CA's collision avoidance, to measure how its contention window and binary exponential backoff keep collisions down as stations are added, and to evaluate 802.11's collision avoidance in NS-2 against the hidden terminal problem, with and without RTS/CTS.

What you need to know before you start

A MAC protocol decides who may transmit when on a channel many nodes share. If two nodes near one receiver transmit at once, both frames are lost: a collision.

CSMA, carrier sense multiple access, is "listen before you talk": a node transmits only when it hears the channel idle. Wired Ethernet adds collision detection (CSMA/CD): a sender listens while it transmits and stops at once if it hears a collision. A radio cannot do that. Its own transmission drowns everything else it could hear, and a collision happens at the receiver, which the sender may not be able to hear at all. So wireless LANs avoid collisions instead: CSMA/CA, the medium access of IEEE 802.11.

How CSMA/CA avoids collisions. A node with a frame to send:

  1. waits until the channel has been idle for a DIFS, a short fixed gap;
  2. then counts down a random backoff, a number of slots drawn from 0 to its contention window, CW, pausing the count whenever the channel is busy;
  3. transmits when the count reaches zero;
  4. waits for an ACK, which the receiver sends after an even shorter gap, a SIFS, so that no other node can start in between.

No ACK means the frame was lost, probably in a collision. The node then doubles its contention window and tries again with a new random backoff: binary exponential backoff. After a success, CW goes back to its minimum. Two nodes that hear each other and have frames at the same moment will almost always pick different backoffs, and the one that picks the smaller wins; the other hears it and waits.

The hidden terminal problem is the case carrier sense cannot solve. Nodes A and C both send to B, between them, but A and C are too far apart to hear each other. Each senses an idle channel and transmits, and their frames collide at B. RTS/CTS is 802.11's answer. Before its data, A sends a short request to send; B answers with a clear to send, which C hears too. Each carries a duration: how long the exchange will take. Every node that hears either one sets its NAV, network allocation vector, to that duration and keeps silent until it expires: virtual carrier sense.

NS-2's 802.11 uses these values (ns-default.tcl and mac-802_11.h):

ParameterNS-2 value
slot time20 microseconds
SIFS10 microseconds
DIFS = SIFS + 2 slots50 microseconds
contention window, minimum and maximum31 and 1023 slots
data and basic rate1 Mb/s
RTSThreshold_0 bytes: RTS/CTS before every unicast frame
retry limits, short and long7 and 4
munotes.in168

Practical 15: The CSMA/CA MAC Protocol

Step 1: implementing CSMA/CA's collision avoidance

csma.py simulates N stations on one channel, all within range of each other, each with a frame always waiting. Time moves a slot at a time while the channel is idle, and a whole transmission at a time when someone sends. Each station counts its backoff down in idle slots; when one reaches zero alone, its frame gets through; when two or more reach zero in the same slot, they collide. The window either doubles after a collision, as 802.11 does, or stays fixed at 31, for comparison.

# csma.py: CSMA/CA's collision avoidance, slot by slot: N stations that always have a frame
# to send share one channel; each waits a random backoff drawn from its contention window.
import random
import sys

SLOT, SIFS, DIFS = 20, 10, 50        # microseconds: ns-2's Mac/802_11 SlotTime_, SIFS_, SIFS + 2 slots
CW_MIN, CW_MAX = 31, 1023            # ns-2's Mac/802_11 CWMin_ and CWMax_
DATA = 192 + (1000 + 28) * 8         # a 1000-byte frame at 1 Mb/s: PLCP preamble and header, MAC header
ACK = 304                            # a 14-byte ACK at 1 Mb/s behind the PLCP (the NS-2 laboratory chapter)
TX = DIFS + DATA + SIFS + ACK        # the channel time one attempt takes, successful or not


def run(n, doubling, seconds=10, seed=1):
    rng = random.Random(seed)
    cw = [CW_MIN] * n
    backoff = [rng.randint(0, CW_MIN) for _ in range(n)]
    clock = attempts = collided = sent = 0
    while clock < seconds * 1e6:
        ready = [i for i in range(n) if backoff[i] == 0]
        if not ready:                          # an idle slot: every counter moves down
            backoff = [b - 1 for b in backoff]
            clock += SLOT
            continue
        attempts += len(ready)
        clock += TX
        if len(ready) == 1:                    # one sender: the frame gets through
            sent += 1
            cw[ready[0]] = CW_MIN
        else:                                  # two or more: all of them collide
            collided += len(ready)
            for i in ready:
                if doubling:
                    cw[i] = min(2 * (cw[i] + 1) - 1, CW_MAX)
        for i in ready:
            backoff[i] = rng.randint(0, cw[i])
    return attempts, collided, sent * 1000 * 8 / (clock / 1e6) / 1e6


print('stations  window            attempts  collided  collision %  throughput')
for n in (2, 5, 10, 20, 50):
    for doubling, name in ((True, 'doubles, 31-1023'), (False, 'fixed at 31')):
        a, c, t = run(n, doubling)
        print('%8d  %-16s  %8d  %8d  %10.1f  %6.3f Mb/s' % (n, name, a, c, 100.0 * c / a, t))
munotes.in169

Practical 15: The CSMA/CA MAC Protocol

TX is the channel time one attempt takes: DIFS, the frame at 1 Mb/s behind its 192-bit physical preamble and header, SIFS, and the ACK. The doubling line is ns-2's own rule (inc_cw in mac-802_11.h): 31, 63, 127, 255, 511, 1023. The simulation is simplified in one way: a frame is retried until it gets through, where 802.11 gives up after its retry limit.

$ python3 csma.py
stations  window            attempts  collided  collision %  throughput
       2  doubles, 31-1023      1154        72         6.2   0.865 Mb/s
       2  fixed at 31           1152        66         5.7   0.869 Mb/s
       5  doubles, 31-1023      1242       224        18.0   0.814 Mb/s
       5  fixed at 31           1290       308        23.9   0.786 Mb/s
      10  doubles, 31-1023      1341       398        29.7   0.754 Mb/s
      10  fixed at 31           1483       643        43.4   0.672 Mb/s
      20  doubles, 31-1023      1462       600        41.0   0.689 Mb/s
      20  fixed at 31           1953      1373        70.3   0.464 Mb/s
      50  doubles, 31-1023      1684       965        57.3   0.575 Mb/s
      50  fixed at 31           3470      3227        93.0   0.194 Mb/s

The random backoff is what avoids collisions. With two stations only about one attempt in sixteen collides: both would have to draw the same slot. But every station added is another chance of a tie, and with a window fixed at 31 the chance of a collision climbs until, at fifty stations, 93.0 per cent of attempts collide and the channel carries 0.194 Mb/s.

Doubling the window is what keeps it working under load. Each collision makes the stations involved spread their next attempts over twice as many slots, so the more crowded the channel, the more spread out the attempts become. At fifty stations the doubling window holds collisions to 57.3 per cent and throughput to 0.575 Mb/s, three times the fixed window's. At two stations the doubling hardly matters: there are few collisions to react to.

Nothing reaches 1 Mb/s, even with two stations: every frame also costs a DIFS, a backoff, a SIFS and an ACK, and 0.865 Mb/s is what is left.

Step 2: 802.11 in NS-2, and a hidden terminal

hidden.tcl places three nodes in a line, 200 m apart. Nodes 0 and 2 both send 1000-byte packets to node 1, in the middle, about fifty a second each. Two arguments choose the case:

# hidden.tcl: two senders, one receiver between them, 802.11; hidden or not, RTS/CTS or not
#   ns hidden.tcl <cs: normal|hidden> <rts: on|off>
set cs  [lindex $argv 0]
set rts [lindex $argv 1]
if {$cs == "hidden"} {
    Phy/WirelessPhy set CSThresh_ [Phy/WirelessPhy set RXThresh_]   ;# sense only as far as receive
}
if {$rts == "off"} {
    Mac/802_11 set RTSThreshold_ 3000     ;# larger than any packet: no RTS/CTS
} else {
    Mac/802_11 set RTSThreshold_ 0        ;# every packet: RTS/CTS (the ns-2 default)
}
set ns [new Simulator]
set tf [open hidden.tr w]
$ns trace-all $tf
set topo [new Topography]
$topo load_flatgrid 500 200
create-god 3
$ns node-config -adhocRouting DumbAgent -llType LL -macType Mac/802_11 \
    -ifqType Queue/DropTail/PriQueue -ifqLen 50 -antType Antenna/OmniAntenna \
    -propType Propagation/TwoRayGround -phyType Phy/WirelessPhy \
    -channel [new Channel/WirelessChannel] -topoInstance $topo \
    -agentTrace ON -routerTrace OFF -macTrace ON -movementTrace OFF
foreach i {0 1 2} x {50 250 450} {
    set node($i) [$ns node]
    $node($i) random-motion 0
    $node($i) set X_ $x
    $node($i) set Y_ 100
    $node($i) set Z_ 0
}
# node 0 and node 2 each send 1000-byte packets to node 1
foreach src {0 2} {
    set udp($src) [new Agent/UDP]
    $ns attach-agent $node($src) $udp($src)
    set sink($src) [new Agent/Null]
    $ns attach-agent $node(1) $sink($src)
    $ns connect $udp($src) $sink($src)
    set cbr($src) [new Application/Traffic/CBR]
    $cbr($src) set packetSize_ 1000
    $cbr($src) set interval_ 0.02
    $cbr($src) set random_ 1
    $cbr($src) attach-agent $udp($src)
    $ns at 1.0 "$cbr($src) start"
    $ns at 11.0 "$cbr($src) stop"
}
$ns at 12.0 "finish"
proc finish {} {
    global ns tf
    $ns flush-trace
    close $tf
    exit 0
}
$ns run
munotes.in170

Practical 15: The CSMA/CA MAC Protocol

Three things in it are new.

No routing protocol. -adhocRouting DumbAgent hands packets straight to the MAC for the node they are addressed to: every node here is one hop from node 1, and a routing protocol's own packets would only get in the way of measuring the MAC.

The MAC trace is on (-macTrace ON), so every RTS, CTS, data frame and ACK is in the trace, and so is every frame the MAC loses.

How to make a hidden terminal. With NS-2's defaults, a node senses a transmission up to 550 m away but receives only up to 250 m (the NS-2 laboratory chapter). Nodes 0 and 2 are 400 m apart, so normally they sense each other, and no two nodes that can both reach node 1 are ever more than 500 m apart: with the default thresholds a hidden terminal cannot exist. Setting CSThresh_ equal to RXThresh_ makes a node sense only as far as it can receive, 250 m, and then nodes 0 and 2 cannot hear each other at all.

mac.awk counts what happened:

# mac.awk: what the MAC did, and what arrived
$1 == "s" && $4 == "AGT" { sent[$3]++ }
$1 == "r" && $4 == "AGT" { got++ }
$1 == "s" && $4 == "MAC" { frames[$7]++ }
$1 == "D" && $4 == "MAC" { drop[$5]++ }
END {
    printf "sent by 0 and 2   %d and %d\n", sent["_0_"], sent["_2_"]
    printf "received by 1     %d\n", got
    printf "MAC frames sent   RTS %d, CTS %d, data %d, ACK %d\n", frames["RTS"], frames["CTS"], frames["cbr"], frames["ACK"]
    printf "MAC drops         collisions %d, retries exhausted %d\n", drop["COL"], drop["RET"]
}
munotes.in171

Practical 15: The CSMA/CA MAC Protocol

The four cases, from runs.sh:

# runs.sh: the four cases, each measured with mac.awk
for cs in normal hidden; do
    for rts in off on; do
        ns hidden.tcl $cs $rts > /dev/null 2>&1
        echo "== $cs carrier sensing, RTS/CTS $rts"
        awk -f mac.awk hidden.tr
    done
done
$ bash runs.sh
== normal carrier sensing, RTS/CTS off
sent by 0 and 2   497 and 508
received by 1     1004
MAC frames sent   RTS 0, CTS 0, data 1004, ACK 1007
MAC drops         collisions 0, retries exhausted 0
== normal carrier sensing, RTS/CTS on
sent by 0 and 2   514 and 489
received by 1     1002
MAC frames sent   RTS 1005, CTS 1005, data 1002, ACK 1005
MAC drops         collisions 0, retries exhausted 0
== hidden carrier sensing, RTS/CTS off
sent by 0 and 2   504 and 497
received by 1     120
MAC frames sent   RTS 0, CTS 0, data 1750, ACK 122
MAC drops         collisions 1631, retries exhausted 187
== hidden carrier sensing, RTS/CTS on
sent by 0 and 2   504 and 498
received by 1     996
MAC frames sent   RTS 1505, CTS 998, data 996, ACK 998
MAC drops         collisions 502, retries exhausted 8

(The CBR sources send at random intervals around 20 ms, random_ 1, so each case sends slightly different numbers of packets.)

When the senders can hear each other, carrier sense and the backoff avoid every collision: 1004 of 1005 packets arrive with RTS/CTS off. Turning RTS/CTS on changes nothing but the cost: 1005 RTS frames and 1005 CTS frames more, for the same delivery.

When they cannot, carrier sense is useless: each senses a quiet channel while the other is transmitting to node 1. With RTS/CTS off, node 1 received only 120 of 1001 packets. There were 1631 collisions, and 187 packets were thrown away after exhausting their retries; the MAC sent 1750 data frames to get 120 through.

RTS/CTS rescues it. With it on, 996 of 1002 packets arrive. There are still collisions, 502 of them, but now they are between RTS frames, not data frames.

Step 3: reading the collisions, and the reservation

The last case run is the hidden one with RTS/CTS on, so its trace is still in hidden.tr. Its very first frames show the one kind of frame RTS/CTS cannot protect:

$ awk '$4 == "MAC" && $2 < 1.0014' hidden.tr | cut -c1-72
s 1.000355000 _2_ MAC  --- 0 ARP 86 [0 ffffffff 2 806] ------- [REQUEST
s 1.000615000 _0_ MAC  --- 0 ARP 86 [0 ffffffff 0 806] ------- [REQUEST
D 1.000615667 _1_ MAC  COL 0 ARP 86 [0 ffffffff 2 806] ------- [REQUEST
D 1.001303667 _1_ MAC  COL 0 ARP 86 [0 ffffffff 0 806] ------- [REQUEST
munotes.in172

Practical 15: The CSMA/CA MAC Protocol

Before sending its first packet, each sender asks, by ARP, for node 1's MAC address. An ARP request is a broadcast, to ffffffff, and broadcasts are sent without RTS/CTS, because there is no single receiver to answer with a CTS. Node 2 starts at 1.000355 s; node 0, which cannot hear it, starts 0.26 ms later; node 1 loses both (COL). RTS/CTS protects only unicast frames.

Later, a complete protected exchange:

$ awk '$4 == "MAC" && $2 >= 1.0133 && $2 < 1.0241' hidden.tr | cut -c1-72
s 1.013355458 _0_ MAC  --- 0 RTS 44 [238e 1 0 0]
r 1.013708125 _1_ MAC  --- 0 RTS 44 [238e 1 0 0]
s 1.013718125 _1_ MAC  --- 0 CTS 38 [2254 0 0 0]
r 1.014022792 _0_ MAC  --- 0 CTS 38 [2254 0 0 0]
s 1.014032792 _0_ MAC  --- 2 cbr 1058 [13a 1 0 800] ------- [0:0 1:0 32
r 1.022497458 _1_ MAC  --- 2 cbr 1000 [13a 1 0 800] ------- [0:0 1:0 32
s 1.022507458 _1_ MAC  --- 0 ACK 38 [0 0 0 0]
r 1.022812125 _0_ MAC  --- 0 ACK 38 [0 0 0 0]
s 1.023402125 _2_ MAC  --- 0 ARP 86 [0 ffffffff 2 806] ------- [REQUEST
r 1.024090792 _1_ MAC  --- 0 ARP 28 [0 ffffffff 2 806] ------- [REQUEST

The first field in the brackets is the frame's duration, in microseconds, in hexadecimal: the time the frame reserves the channel for, after itself. The times in the trace give every part of it. The data frame left node 0 at 1.014032792 s and finished arriving at 1.022497458 s, 8464.666 microseconds, of which 0.667 microseconds is the signal crossing 200 m: it takes 8464 microseconds to send. An ACK or a CTS takes 304, and the gap between frames, SIFS, is 10. So:

  • the CTS reserves the rest of the exchange, SIFS, data, SIFS, ACK: (2254)₁₆ = 8788 = 10 + 8464 + 10 + 304;
  • the RTS reserves the CTS as well: (238E)₁₆ = 9102 = 10 + 304 + 10 + 8464 + 10 + 304.

This is how the hidden node is kept quiet. Node 2 cannot hear node 0's RTS, but it can hear node 1's CTS, which tells it the channel is taken for the next 8788 microseconds. NS-2 does not write a trace line for a frame at a node it is not addressed to, so the CTS arriving at node 2 is not in the trace; its effect is. Node 2 sends nothing from the CTS until the ACK has finished at 1.022812 s, and its next frame goes at 1.023402 s, after a DIFS and a backoff.

munotes.in173

Practical 15: The CSMA/CA MAC Protocol

Why collisions of RTS frames are cheap. An RTS takes 352 microseconds to send, the data frame 8464. When two hidden senders' RTS frames collide, 352 microseconds are lost, not 8464, and both simply try again later.

Procedure

  1. Write csma.py, simulating N saturated stations with a random backoff from a contention window, doubling after a collision or fixed.
  2. Run it for 2 to 50 stations, and compare the collision rate and throughput of the two windows.
  3. Write hidden.tcl: three nodes in a line, two sending to the one in the middle, 802.11 with no routing and the MAC trace on.
  4. Run the four cases, normal or hidden carrier sensing, RTS/CTS off or on, and count deliveries, frames and MAC drops with mac.awk.
  5. Find the collision of two broadcasts in the trace, and one full RTS, CTS, data and ACK exchange.
  6. Recompute the RTS and CTS durations from the frame times, and explain how the hidden node was kept silent.

Observations

StationsCollisions, window doublingCollisions, window fixed at 31Throughput, doublingThroughput, fixed
26.2 %5.7 %0.865 Mb/s0.869 Mb/s
1029.7 %43.4 %0.754 Mb/s0.672 Mb/s
5057.3 %93.0 %0.575 Mb/s0.194 Mb/s
NS-2, 802.11Received by node 1MAC collisionsData frames sent
normal carrier sensing, RTS/CTS off1004 of 100501004
normal carrier sensing, RTS/CTS on1002 of 100301002
hidden, RTS/CTS off120 of 100116311750
hidden, RTS/CTS on996 of 1002502, all RTS frames and broadcasts996

Result

CSMA/CA's collision avoidance was implemented in a slot-level simulation with NS-2's 802.11 constants. With two stations about 6 per cent of attempts collided. As stations were added the collision rate rose, and binary exponential backoff was what held it down: at fifty stations the doubling window kept collisions to 57.3 per cent and throughput to 0.575 Mb/s, against 93.0 per cent and 0.194 Mb/s with a window fixed at 31. In NS-2's 802.11, senders that could sense each other lost almost nothing with or without RTS/CTS, so RTS/CTS was pure overhead there. When the carrier-sense range was reduced to make the two senders hidden from each other, only 120 of 1001 packets arrived without RTS/CTS, and 996 of 1002 with it: the CTS's duration field, 8788 microseconds, recomputed exactly from the frame times, kept the hidden sender silent through each exchange. Broadcast frames, which RTS/CTS cannot protect, still collided.

Where marks are lost

Calling it collision detection. A radio cannot hear a collision while it transmits, and the collision happens at the receiver. 802.11 avoids collisions and learns of them only from a missing ACK.

munotes.in174

Practical 15: The CSMA/CA MAC Protocol

Expecting hidden terminals with NS-2's defaults. The default carrier-sense range, 550 m, is more than twice the receive range, so two nodes that can both reach a receiver always sense each other. Lower CSThresh_, and say why.

Forgetting that NS-2 turns RTS/CTS on. Mac/802_11 set RTSThreshold_ 0 is the default: every unicast frame goes with RTS/CTS. To see the protocol without it, set the threshold above the frame size.

Saying RTS/CTS prevents all collisions. RTS frames still collide, and broadcasts, which have no single receiver to send a CTS, are not protected at all.

Measuring a MAC with a routing protocol running. Routing packets add traffic and collisions of their own. For one-hop experiments use DumbAgent.

Reading the duration field as decimal. It is hexadecimal and in microseconds: 2254 is 8788 microseconds.

For the journal

Write: aim; CSMA, and why wireless uses collision avoidance, not detection; the four steps of CSMA/CA; binary exponential backoff; the hidden terminal problem and RTS/CTS with the NAV; the table of NS-2's 802.11 parameters; csma.py with its output, and what the two windows show; hidden.tcl, why CSThresh_ was changed, and the four runs; the two trace extracts, with the RTS and CTS durations worked out from the frame times; observations; result.

Quick revision

  • CSMA: listen before talking. Wireless cannot detect collisions, so 802.11 avoids them: CSMA/CA.
  • CSMA/CA: wait DIFS, count down a random backoff of 0 to CW slots (paused while busy), send, wait for the ACK after SIFS.
  • No ACK: double the contention window and retry; success: reset it. NS-2: 31 to 1023.
  • More stations, more collisions; the doubling window adapts, a fixed one collapses.
  • Hidden terminal: two senders that cannot hear each other collide at a receiver both can reach.
  • RTS/CTS: the CTS's duration sets the NAV of every node that hears it; they stay silent.
  • NS-2: carrier sense 550 m, reception 250 m, so no hidden terminals unless CSThresh_ is lowered.
  • NS-2's default RTSThreshold_ is 0: RTS/CTS on every unicast frame.
  • RTS/CTS cannot protect broadcasts.
  • DIFS 50, SIFS 10, slot 20 microseconds; ACK and CTS 304, RTS 352, 1000-byte data 8464.

Questions you must be able to answer

1. Why does a wireless LAN use collision avoidance and not collision detection? A radio cannot hear other transmissions while it is transmitting, because its own signal drowns them, and a collision happens at the receiver, which the sender may not be able to hear. So the sender cannot detect a collision; it can only try to avoid one, and learn of a failure from a missing ACK.

2. What is the contention window, and what happens to it after a collision and after a success? The range from which a station draws its random backoff, 0 to CW slots. After a collision the station doubles it (in NS-2, 31, 63, 127 up to 1023) and draws again; after a success it goes back to the minimum.

munotes.in175

Practical 15: The CSMA/CA MAC Protocol

3. In csma.py, why does the doubling window beat the fixed window at fifty stations but not at two? At fifty stations collisions are frequent, and each one makes the colliding stations spread their next attempts over more slots, so attempts collide less. At two stations there are few collisions for the doubling to react to.

4. What is the hidden terminal problem? Two senders out of each other's range both transmit to a receiver in range of both. Each senses the channel idle while the other is sending, so their frames collide at the receiver.

5. Why could hidden.tcl produce no hidden terminal until CSThresh_ was changed? Because NS-2 senses carriers up to 550 m and receives only up to 250 m. Two nodes that can both reach node 1 are at most 500 m apart, so they always sense each other. Setting CSThresh_ equal to RXThresh_ reduces the sensing range to 250 m.

6. How does RTS/CTS stop the hidden node from transmitting? The receiver's CTS, which the hidden node can hear, carries the time the exchange will take. The hidden node sets its NAV to that time and stays silent until it expires.

7. Work out the CTS duration in the trace, (2254)₁₆, from the frame times. SIFS, the data frame, SIFS and the ACK: 10 + 8464 + 10 + 304 = 8788 microseconds, which is (2254)₁₆.

8. Why was RTS/CTS useless in the normal case? The two senders could hear each other, so carrier sense and the random backoff already prevented collisions. RTS/CTS added two frames to every exchange and delivered nothing more.

9. With RTS/CTS on, there were still 502 collisions. Why did they matter so little? They were collisions of RTS frames, and of broadcasts, not of data frames. An RTS takes 352 microseconds to send against 8464 for a data frame, so a collision wastes a small fraction of the time, and the senders simply try again.

10. Why did the first two ARP requests collide even with RTS/CTS on? An ARP request is a broadcast. Broadcasts are sent without RTS/CTS, because there is no single receiver to answer with a CTS, so the two hidden senders' requests collided at node 1.

11. What does DumbAgent do, and why use it here? It passes packets straight to the MAC for a node one hop away, with no routing protocol. A routing protocol's own packets would add traffic and collisions to an experiment about the MAC.

munotes.in176

Practical 15: The CSMA/CA MAC Protocol

12. Why is the throughput in csma.py below 1 Mb/s even with two stations and no collisions? Because each frame also costs a DIFS, the backoff slots, a SIFS and an ACK, during which no data is carried.

Contents This chapter on its own page

munotes.in177

Chapter Nineteen

Practical 16: TDMA Slot Allocation and Its Energy against CSMA

Syllabus topic Module 2, "Simulation of TDMA-based MAC Protocol: Simulate TDMA slot allocation and compare energy efficiency with CSMA."

Aim

To implement TDMA slot allocation for a multi-hop sensor network, to simulate NS-2's TDMA MAC, and to compare its energy consumption, delivery and delay with those of a CSMA MAC, 802.11, on the same sensors and traffic.

What you need to know before you start

TDMA, time division multiple access, divides time into repeating frames, and each frame into slots. Each node owns a slot and transmits only in it, so two nodes that would interfere never transmit at once: there is nothing to contend for and nothing to collide. CSMA (Practical 15) is the opposite approach: nobody owns the channel, and every node listens and competes for it whenever it has a frame.

Slot allocation is deciding who owns which slot. The simplest rule gives every node a slot of its own, so a frame of N nodes has N slots. But in a multi-hop network two nodes far apart can safely share a slot, which makes frames shorter and every node's wait shorter: spatial reuse. How far apart must they be? If two nodes within range of each other transmit together, their neighbours hear a collision; and if two nodes out of each other's range share a neighbour, that neighbour hears both: Practical 15's hidden terminals. So two nodes may share a slot only if they are more than two hops apart.

Why TDMA saves energy. A sensor's radio has four states, and Practical 1 measured what each costs on a MICAz, at 3 V:

Radio stateCurrentPower (at 3 V)
transmitting at 0 dBm17.4 mA0.0522 W
receiving19.7 mA0.0591 W
listening, idle19.7 mA0.0591 W
asleep1 microampere0.000003 W

Listening costs as much as receiving, and a CSMA node must listen all the time, since a frame for it could start at any moment. A TDMA node knows when its frames can come, and sleeps the rest of the time. The price is delay: a packet waits for its node's slot.

Step 1: allocating slots in a sensor grid

slots.py places 25 sensors in a 5 by 5 grid, 200 m apart. With NS-2's 250 m range each node reaches its four neighbours, left, right, above and below, but not the diagonal ones, 283 m away. It finds every pair that conflicts, within two hops, and gives each node, in turn, the lowest slot no conflicting node already has: a greedy allocation.

# slots.py: TDMA slot allocation for a sensor grid: no two nodes within two hops share a slot
import math

SIDE, SPACING, RANGE = 5, 200.0, 250.0      # a 5 by 5 grid, 200 m apart; NS-2's 250 m range
nodes = [(r, c) for r in range(SIDE) for c in range(SIDE)]
pos = {n: (n[1] * SPACING, n[0] * SPACING) for n in nodes}
nbrs = {a: {b for b in nodes if b != a and math.dist(pos[a], pos[b]) <= RANGE} for a in nodes}
# two nodes conflict if one hears the other, or both reach a common neighbour
conflict = {a: nbrs[a] | {c for b in nbrs[a] for c in nbrs[b] if c != a} for a in nodes}

slot = {}
for a in nodes:                              # greedy: the lowest slot no conflicting node has
    taken = {slot[b] for b in conflict[a] if b in slot}
    slot[a] = min(s for s in range(len(nodes)) if s not in taken)

frame = max(slot.values()) + 1
print('slot of every node (row by row):')
for r in range(SIDE):
    print('   ' + '  '.join(str(slot[(r, c)]) for c in range(SIDE)))
clashes = sum(1 for a in nodes for b in conflict[a] if slot[a] == slot[b])
print('frame length: %d slots for %d nodes (one slot each would need %d)' % (frame, len(nodes), len(nodes)))
print('pairs within two hops sharing a slot: %d' % clashes)
print('each node may send once every %d slots instead of once every %d' % (frame, len(nodes)))

# can it be done in fewer? a node and its four neighbours are all within two hops of each other
centre = (2, 2)
group = [centre] + sorted(nbrs[centre])
print('a node and its %d neighbours all conflict with each other: %s' % (
    len(group) - 1, all(b in conflict[a] for a in group for b in group if a != b)))
# so no schedule can use fewer than 5 slots; this one uses exactly 5
slot5 = {(r, c): (c + 2 * r) % 5 for (r, c) in nodes}
clashes5 = sum(1 for a in nodes for b in conflict[a] if slot5[a] == slot5[b])
print('slot = (column + 2 x row) mod 5: %d slots, pairs sharing a slot: %d' % (len(set(slot5.values())), clashes5))
munotes.in178

Practical 16: TDMA Slot Allocation and Its Energy against CSMA

$ python3 slots.py
slot of every node (row by row):
   0  1  2  0  1
   2  3  4  5  2
   1  0  6  1  0
   3  2  5  3  4
   0  1  4  0  1
frame length: 7 slots for 25 nodes (one slot each would need 25)
pairs within two hops sharing a slot: 0
each node may send once every 7 slots instead of once every 25
a node and its 4 neighbours all conflict with each other: True
slot = (column + 2 x row) mod 5: 5 slots, pairs sharing a slot: 0

Spatial reuse shortens the frame from 25 slots to 7. Nodes more than two hops apart share slots: slot 0, for instance, belongs to six nodes spread over the grid. The program checks every conflicting pair and finds none sharing a slot. A node now waits at most 7 slots for its turn instead of 25.

munotes.in179

Practical 16: TDMA Slot Allocation and Its Energy against CSMA

Greedy is not the best. A node and its four neighbours are all within two hops of each other (any two neighbours share the node between them), so those five need five different slots, and no schedule can use fewer than 5. One that uses exactly 5 is the formula (column + 2 × row) mod 5, which the program checks has no conflicts either. Slot allocation is a graph-colouring problem, and greedy colouring, fast and simple, does not always find the fewest colours.

Step 2: TDMA in NS-2

NS-2's TDMA MAC, Mac/Tdma, allocates slots the simple way, as its source says: a "much simplified centralized scheduling algorithm for single hop topology, like WLAN" (mac-tdma.cc). Every node gets the next slot in the order the nodes were created (re_schedule()), so a frame has one slot per node, plus one at its start for a preamble: a table, kept in the simulator's memory rather than transmitted, in which each node writes whom it will send to in its slot this frame. A slot is long enough for one 1500-byte packet (Mac/Tdma set slot_packet_len_ 1500).

What matters for energy is what the node does with its radio in each slot (slotHandler()):

SlotRadio
the preamble sloton for the whole slot
its own slot, with a packet to sendon, to send it
a slot whose owner will send to it, or broadcaston, to receive
every other slotoff: the PHY is put to sleep

Because the schedule is for one hop, the experiment is one hop: six sensors in a circle 150 m round a sink, each reporting a 64-byte reading every second.

# energy.tcl: six sensors around a sink report a reading each second; the MAC is chosen on the
# command line, and every radio draws a MICAz's power (Practical 1)
#   ns energy.tcl <Mac/Tdma|Mac/802_11>
set val(mac)   [lindex $argv 0]
set val(n)     7                 ;# node 0 is the sink, nodes 1 to 6 the sensors
set val(stop)  100.0
set ns [new Simulator]
set tf [open energy.tr w]
$ns trace-all $tf
set topo [new Topography]
$topo load_flatgrid 400 400
create-god $val(n)
$ns node-config -adhocRouting DumbAgent -llType LL -macType $val(mac) \
    -ifqType Queue/DropTail/PriQueue -ifqLen 50 -antType Antenna/OmniAntenna \
    -propType Propagation/TwoRayGround -phyType Phy/WirelessPhy \
    -channel [new Channel/WirelessChannel] -topoInstance $topo \
    -agentTrace ON -routerTrace OFF -macTrace OFF -movementTrace OFF \
    -energyModel EnergyModel -initialEnergy 10.0 \
    -txPower 0.0522 -rxPower 0.0591 -idlePower 0.0591 -sleepPower 0.000003
for {set i 0} {$i < $val(n)} {incr i} {
    set node($i) [$ns node]
    $node($i) random-motion 0
    if {$i == 0} {
        set x 200; set y 200
    } else {
        set a [expr ($i - 1) * 3.14159265 / 3]
        set x [expr 200 + 150 * cos($a)]
        set y [expr 200 + 150 * sin($a)]
    }
    $node($i) set X_ $x
    $node($i) set Y_ $y
    $node($i) set Z_ 0
}
for {set i 1} {$i < $val(n)} {incr i} {
    set udp($i) [new Agent/UDP]
    $ns attach-agent $node($i) $udp($i)
    set sink($i) [new Agent/Null]
    $ns attach-agent $node(0) $sink($i)
    $ns connect $udp($i) $sink($i)
    set cbr($i) [new Application/Traffic/CBR]
    $cbr($i) set packetSize_ 64
    $cbr($i) set interval_ 1.0
    $cbr($i) attach-agent $udp($i)
    $ns at [expr 1.0 + $i * 0.1] "$cbr($i) start"
    $ns at 99.0 "$cbr($i) stop"
}
$ns at $val(stop) "finish"
proc finish {} {
    global ns tf node val
    for {set i 0} {$i < $val(n)} {incr i} {
        puts [format "node %d  energy left %.4f J" $i [$node($i) energy]]
    }
    $ns flush-trace
    close $tf
    exit 0
}
$ns run
munotes.in180

Practical 16: TDMA Slot Allocation and Its Energy against CSMA

The energy model. -energyModel EnergyModel gives every node a battery, here 10 J, and the four powers charge it for the time its radio spends in each state. $node energy reads what is left. -idlePower must be set. ns-2's radio charges nothing for listening unless told to (P_idle_ = 0.0, wireless-phy.cc line 119), and without it a CSMA node that listens all day would appear to cost nothing; here listening costs what receiving does, as it does on a MICAz.

report.awk measures delivery and delay, and runs.sh runs both MACs and prints, for the sink (node 0) and one sensor (node 1), the energy fields of each node's last trace line: [energy left, ei idle, es sleep, et transmit, er receive], the joules spent in each state so far (format_mac_common, trace/cmu-trace.cc).

# report.awk: readings delivered to the sink, and their average delay
$1 == "s" && $4 == "AGT" { sent++; t[$6] = $2 }
$1 == "r" && $4 == "AGT" { got++; delay += $2 - t[$6] }
END { printf "readings delivered %d of %d, average delay %.1f ms\n", got, sent, delay / got * 1000 }
# runs.sh: the same sensors under each MAC: energy left, delivery, and where the energy went
for m in Mac/802_11 Mac/Tdma; do
    echo "== $m"
    ns energy.tcl $m 2> /dev/null | grep "^node"
    awk -f report.awk energy.tr
    for n in 0 1; do
        echo "node $n spent: $(grep " _${n}_ " energy.tr | tail -1 | grep -o "\[energy[^]]*\]")"
    done
done
$ bash runs.sh
== Mac/802_11
node 0  energy left 4.1751 J
node 1  energy left 4.1735 J
node 2  energy left 4.1735 J
node 3  energy left 4.1735 J
node 4  energy left 4.1735 J
node 5  energy left 4.1735 J
node 6  energy left 4.1735 J
readings delivered 588 of 588, average delay 2.1 ms
node 0 spent: [energy 4.175113 ei 5.759 es 0.000 et 0.019 er 0.047]
node 1 spent: [energy 4.232608 ei 5.701 es 0.000 et 0.007 er 0.060]
== Mac/Tdma
node 0  energy left 9.0480 J
node 1  energy left 9.2238 J
node 2  energy left 9.2238 J
node 3  energy left 9.2238 J
node 4  energy left 9.2238 J
node 5  energy left 9.2238 J
node 6  energy left 9.2238 J
readings delivered 588 of 588, average delay 54.6 ms
node 0 spent: [energy 9.058275 ei 0.925 es 0.000 et 0.000 er 0.016]
node 1 spent: [energy 9.238376 ei 0.759 es 0.000 et 0.002 er 0.000]
munotes.in181

Practical 16: TDMA Slot Allocation and Its Energy against CSMA

Both delivered every reading. 588 of 588: six sensors, 98 readings each.

CSMA spent its battery listening. Every 802.11 node used about 5.83 J of its 10 J in 100 s. The breakdown says where: node 1 had spent 5.701 J idle, 0.060 J receiving and 0.007 J transmitting. Listening for 100 s at 0.0591 W is 5.91 J, and the radio was never asleep: es is 0. Sending its 98 readings cost node 1 seven thousandths of a joule; waiting for them cost eight hundred times that.

TDMA slept. Each TDMA sensor used 10 - 9.2238 = 0.7762 J, 13 per cent of what an 802.11 sensor used: it kept its radio on only in the preamble slot and its own, and the energy it spent was almost all listening in those slots, 0.759 J. Its sleep cost nothing visible: 1 microampere at 3 V for a whole run is 0.0003 J. The sink used more, 0.95 J, because it also listens in the slot of every sensor that sends to it.

The price is delay. A reading waited 54.6 ms on average under TDMA, against 2.1 ms under 802.11. Under TDMA a node may send only in its own slot of a frame, and only if it announced the packet in that frame's preamble, so a reading that arrives just after its chance waits most of a frame, and longer, for the next.

Two things to know about the numbers. The energy fields on each node's last trace line are what it had spent by that moment: node 1's last line is its last send, after which its 802.11 radio went on listening to the others, so its final reading, 4.1735 J, is lower than the 4.232608 on that line. And ns-2 charges energy when a radio changes state, so the 802.11 nodes' last 1.4 s of listening, after the last packet, had not been charged when finish read them: about 0.08 J, which would make 802.11's figure slightly worse, not better.

munotes.in182

Practical 16: TDMA Slot Allocation and Its Energy against CSMA

Procedure

  1. Write slots.py: a 5 by 5 sensor grid, the two-hop conflict rule, and a greedy slot allocation.
  2. Run it, check that no conflicting pair shares a slot, and compare the frame with one slot per node.
  3. Show that no schedule can use fewer than 5 slots, and check one that uses exactly 5.
  4. Read how NS-2's Mac/Tdma allocates slots and when it turns the radio off.
  5. Write energy.tcl: six sensors and a sink, the MICAz's radio powers, -idlePower included.
  6. Run it with Mac/802_11 and Mac/Tdma; compare energy left, delivery, delay and where each radio's energy went.

Observations

Slot allocation, 25 sensors in a gridSlots in a frame
one slot per node25
greedy, two-hop rule7
lower bound, and the formula (column + 2 × row) mod 55
100 s, six sensors and a sink, 10 J each802.11 (CSMA)TDMA
Energy left, each sensor4.1735 J9.2238 J
Energy left, the sink4.1751 J9.0480 J
Energy spent listening, node 15.701 J0.759 J
Energy spent transmitting, node 10.007 J0.002 J
Readings delivered588 of 588588 of 588
Average delay2.1 ms54.6 ms

Result

A TDMA slot allocation was implemented for 25 sensors in a grid, allowing two nodes to share a slot only if they were more than two hops apart. The greedy allocation needed 7 slots where one slot per node would need 25; the program showed that no allocation could use fewer than 5, since a node and its four neighbours all conflict, and checked a 5-slot schedule, (column + 2 × row) mod 5. In NS-2, the same six sensors reported to a sink under TDMA and under 802.11, with the MICAz's radio powers. Both delivered all 588 readings. Each 802.11 sensor spent 5.83 J of its 10 J in 100 s, almost all of it listening; each TDMA sensor spent 0.78 J, 13 per cent as much, because its radio slept outside its slots. TDMA's cost was delay: 54.6 ms per reading against 2.1 ms.

Where marks are lost

Leaving out -idlePower. ns-2 then charges nothing for listening, and a CSMA node that listens all the time seems to cost as little as a TDMA node that sleeps. Idle listening is exactly the cost the comparison is about.

Letting two nodes two hops apart share a slot. Their common neighbour hears both at once. The rule is more than two hops apart.

Calling greedy allocation optimal. Here it used 7 slots where 5 suffice. Say what the lower bound is, and why.

munotes.in183

Practical 16: TDMA Slot Allocation and Its Energy against CSMA

Expecting NS-2's Mac/Tdma to reuse slots. It gives every node its own slot, for one hop. Spatial reuse is in slots.py, not in NS-2.

Reading a trace line's energy as the final figure. It is what the node had spent by that line. Read the energy left with $node energy at the end.

Reporting TDMA's energy without its delay. Every joule saved by sleeping is paid for by waiting for a slot. Give both.

For the journal

Write: aim; TDMA, frames and slots, and how it differs from CSMA; slot allocation and the two-hop rule; the table of the MICAz's radio powers; slots.py with its output, the lower bound and the 5-slot formula; how NS-2's Mac/Tdma allocates slots and switches its radio; energy.tcl, and why -idlePower is set; the output of runs.sh, with where the energy went; observations; result.

Quick revision

  • TDMA: time in frames, frames in slots; each node sends only in its own slot; no contention, no collisions.
  • Slot allocation: who owns which slot. Spatial reuse: nodes more than two hops apart may share one.
  • Allocation is graph colouring; greedy is quick but not always the fewest slots.
  • 5 by 5 grid: 25 slots one each, 7 greedy, 5 at best (a node and its four neighbours all conflict).
  • MICAz at 3 V: transmit 0.0522 W, receive and listen 0.0591 W, sleep 0.000003 W.
  • CSMA listens all the time: idle listening is most of its energy.
  • NS-2 Mac/Tdma: one slot per node for one hop, plus a preamble; radio off in slots it neither sends nor receives in.
  • Set -idlePower: ns-2's default charges nothing for listening.
  • Here: 802.11 used 5.83 J a sensor, TDMA 0.78 J; delay 2.1 ms against 54.6 ms.

Questions you must be able to answer

1. How does TDMA avoid collisions? Each node transmits only in a slot of its own, and nodes that could interfere never share a slot, so no two of them are ever on the air together.

2. Why may two nodes share a slot only if they are more than two hops apart? If they are one hop apart they hear each other's transmissions directly; if they are two hops apart they share a neighbour, which would hear both at once. Only beyond two hops can neither case happen.

3. What did greedy allocation give for the 5 by 5 grid, and what is the best possible? Greedy gave 7 slots. The best is 5: a node and its four neighbours are all within two hops of each other, so they need 5 different slots, and the schedule (column + 2 × row) mod 5 uses exactly 5 with no conflict.

munotes.in184

Practical 16: TDMA Slot Allocation and Its Energy against CSMA

4. How does NS-2's Mac/Tdma allocate slots? Each node gets the next slot in the order the nodes were created, one slot per node, with a preamble slot at the start of each frame in which nodes announce whom they will send to. It is meant for single-hop networks and does not reuse slots.

5. When does a node's radio sleep under NS-2's TDMA? In every slot in which it neither sends nor is named as a receiver. It is on in the preamble slot, in its own slot when it has a packet, and in slots whose owner will send to it or broadcast.

6. Why did every 802.11 node spend almost the same energy, whether it sent or not? Because nearly all of it was listening: an 802.11 radio listens all the time, and listening costs as much as receiving. Transmitting its readings cost node 1 only 0.007 J of the 5.8 J it used.

7. What happens if -idlePower is left out, and why? ns-2's radio then charges nothing for idle listening (its default is 0 W), so the 802.11 nodes seem to use almost no energy, and the comparison with TDMA hides the very cost TDMA saves.

8. How much energy did a TDMA sensor use, as a share of an 802.11 sensor's? 0.7762 J against 5.8265 J, about 13 per cent.

9. Why did the sink use more energy than the sensors under TDMA? Because it has to listen in the slot of every sensor that sends to it, as well as in the preamble, while a sensor listens only in the preamble and its own slot.

10. What is TDMA's cost, and why? Delay: 54.6 ms per reading here against 2.1 ms. A node may send only in its own slot, after announcing the packet in the frame's preamble, so a reading waits for its node's next chance.

11. What would TDMA gain from the spatial reuse of slots.py? Shorter frames: 7 slots instead of 25 for the grid, so every node's turn comes round more than three times as often, and readings wait correspondingly less.

12. Why is the 802.11 energy figure slightly too low? ns-2 charges energy when a radio changes state. After the last packet, at about 98.6 s, the 802.11 radios went on listening, but nothing charged them for the last 1.4 s before finish read the energy: about 0.08 J more.

Contents This chapter on its own page

munotes.in185

Chapter Twenty

Practical 17: An Energy-Aware Duty-Cycling MAC for Sensor Networks

Syllabus topic Module 2, "Energy-Aware MAC Protocol for WSN: Implement duty-cycling MAC protocol and evaluate energy conservation in sensor networks."

Aim

To implement a duty-cycling MAC model for a sensor network and evaluate its energy and delay, and to measure the energy conservation and delay of NS-2's S-MAC at several duty cycles against an always-listening 802.11 radio.

What you need to know before you start

Idle listening is a sensor node's biggest energy cost. Practical 1 measured it and Practical 16 saw it: a MICAz radio listening for a packet that has not come draws as much as one receiving, 19.7 mA, while asleep it draws 1 microampere. A radio that listens all the time empties two AA cells in under a week.

Duty cycling is the answer: the radio sleeps most of the time and wakes on a regular schedule to listen. The duty cycle is the fraction of time it is awake. A radio awake 10 per cent of the time spends about a tenth of the energy on listening. The cost is latency: a packet has to wait until the receiver is awake, and in a multi-hop network it waits again at every hop.

S-MAC, Sensor-MAC, is the classic duty-cycling MAC for sensor networks. NS-2's implementation describes itself in ten points (mac/smac.h); the ones that matter here:

S-MAC doesHow
a periodic listen and sleep scheduleeach cycle begins with a listen period, then the radio sleeps; the duty cycle sets how long the sleep is
keeps neighbours on the same scheduleat boot a node listens for a whole SYNC period, then sends a SYNC packet announcing its schedule; a node that hears one first follows that schedule instead
finds new neighboursevery node "periodically listens for a whole period of the SYNCPERIOD"
unicastRTS, CTS, data, ACK, inside the listen period, as in 802.11
saves more energya node goes to sleep when a neighbour's RTS or CTS shows it is not involved in the exchange

NS-2's S-MAC cycle, worked out from its source (smac.cc). S-MAC keeps time in 1 ms ticks and models a 20 kbit/s mote radio. Its listen period has two parts: a SYNC part, a DIFS of 10 ms, a contention window of 31 slots of 1 ms, a SYNC packet of 10.2 ms and a guard time of 4 ms, 55.2 ms in all; and a data part, a DIFS, 63 slots, an RTS of 11 ms and the guard time, 88 ms. So the listen period is 55.2 + 88 = 143.2 ms, and

  • cycle = listen period × 100 ÷ duty cycle, plus 1 tick;
  • sleep = cycle - listen period.

At a 10 per cent duty cycle the cycle is 1433 ms, with 1289.8 ms of sleep.

munotes.in186

Practical 17: An Energy-Aware Duty-Cycling MAC for Sensor Networks

Step 1: implementing duty cycling

dutycycle.py models an ideal duty-cycling MAC with S-MAC's listen period: every node listens for 0.1432 s at the start of each cycle and sleeps for the rest, and a packet can cross one hop only while the nodes are listening. For each duty cycle it gives the energy a radio uses in a day, how long two AA cells (3 Ah at 3 V) would last it, and the mean delay of a reading taken at a random moment, over one hop and over five.

# dutycycle.py: a duty-cycled MAC, S-MAC style: every node listens for LISTEN seconds at the start
# of each cycle and sleeps the rest; a packet can cross one hop only while the nodes are listening
import random

LISTEN = 0.1432                  # s: ns-2 S-MAC's listen period (smac.cc), 55.2 ms sync + 88 ms data
P_LISTEN, P_SLEEP = 0.0591, 0.000003   # W: a MICAz radio listening and asleep (Practical 1)
DAY = 24 * 3600


def latency(duty, hops, packets=2000, seed=1):
    """Mean time for a packet made at a random moment to cross `hops` hops, one hop per cycle."""
    cycle = LISTEN / duty
    rng = random.Random(seed)
    total = 0.0
    for _ in range(packets):
        made = rng.uniform(0, cycle)             # when in the cycle the reading is taken
        wait = cycle - made                      # to the next listen period
        total += wait + (hops - 1) * cycle       # then one hop in each listen period
    return total / packets


print('duty cycle  cycle      energy a day   battery life   delay, 1 hop   delay, 5 hops')
for duty in (0.5, 0.2, 0.1, 0.05, 0.01):
    cycle = LISTEN / duty
    watts = duty * P_LISTEN + (1 - duty) * P_SLEEP
    joules = watts * DAY
    life = 2 * 1.5 * 3.0 * 3600 / watts / DAY          # two AA cells: 1.5 V, 3 Ah each
    print('%8.0f %%  %6.3f s  %9.1f J  %9.1f days  %10.3f s  %12.3f s'
          % (duty * 100, cycle, joules, life, latency(duty, 1), latency(duty, 5)))
$ python3 dutycycle.py
duty cycle  cycle      energy a day   battery life   delay, 1 hop   delay, 5 hops
      50 %   0.286 s     2553.2 J       12.7 days       0.143 s         1.288 s
      20 %   0.716 s     1021.5 J       31.7 days       0.356 s         3.220 s
      10 %   1.432 s      510.9 J       63.4 days       0.713 s         6.441 s
       5 %   2.864 s      255.6 J      126.8 days       1.425 s        12.881 s
       1 %  14.320 s       51.3 J      631.3 days       7.127 s        64.407 s

Energy falls in proportion to the duty cycle. Halve the time awake and the energy halves, and the batteries last twice as long: from under two weeks at 50 per cent to 631 days at 1 per cent. (A radio listening all the time would last 6.3 days; these figures are for the radio alone, without the processor.)

munotes.in187

Practical 17: An Energy-Aware Duty-Cycling MAC for Sensor Networks

Delay rises in the same proportion. A reading waits, on average, half a cycle for the next listen period, so each halving of the duty cycle doubles the wait at every hop. Over five hops the wait adds up: 6.4 s at 10 per cent, over a minute at 1 per cent. Choosing a duty cycle is choosing between battery life and delay.

Step 2: S-MAC in NS-2

smac.tcl has one sensor 150 m from a sink, reporting a 64-byte reading every 10 s. The argument chooses the MAC: 802.11, which listens all the time, or a duty cycle for S-MAC.

# smac.tcl: one sensor reports a reading every 10 s to a sink 150 m away, under 802.11 or under
# S-MAC at a chosen duty cycle; both radios draw a MICAz's power (Practical 1)
#   ns smac.tcl <802.11 | duty cycle in per cent>
set arg [lindex $argv 0]
if {$arg == "802.11"} {
    set val(mac) Mac/802_11
} else {
    set val(mac) Mac/SMAC
    Mac/SMAC set syncFlag_ 1          ;# sleep and wake: ns-default.tcl leaves it at 0
    Mac/SMAC set dutyCycle_ $arg
}
set ns [new Simulator]
set tf [open smac.tr w]
$ns trace-all $tf
set topo [new Topography]
$topo load_flatgrid 300 300
create-god 2
$ns node-config -adhocRouting DumbAgent -llType LL -macType $val(mac) \
    -ifqType Queue/DropTail/PriQueue -ifqLen 50 -antType Antenna/OmniAntenna \
    -propType Propagation/TwoRayGround -phyType Phy/WirelessPhy \
    -channel [new Channel/WirelessChannel] -topoInstance $topo \
    -agentTrace ON -routerTrace OFF -macTrace ON -movementTrace OFF \
    -energyModel EnergyModel -initialEnergy 30.0 \
    -txPower 0.0522 -rxPower 0.0591 -idlePower 0.0591 -sleepPower 0.000003
foreach i {0 1} x {75 225} {
    set node($i) [$ns node]
    $node($i) random-motion 0
    $node($i) set X_ $x
    $node($i) set Y_ 150
    $node($i) set Z_ 0
}
set udp [new Agent/UDP]
$ns attach-agent $node(1) $udp
set sink [new Agent/Null]
$ns attach-agent $node(0) $sink
$ns connect $udp $sink
set cbr [new Application/Traffic/CBR]
$cbr set packetSize_ 64
$cbr set interval_ 10.0
$cbr attach-agent $udp
$ns at 50.0 "$cbr start"                ;# once the schedules have settled
$ns at 250.5 "$cbr stop"
# energy 0.1 s after the first reading and 0.1 s after the last: 200 s apart
proc energy_at {tag} {
    global node e
    foreach i {0 1} { set e($tag,$i) [$node($i) energy] }
}
$ns at 50.1 "energy_at start"
$ns at 250.1 "energy_at end"
$ns at 256.0 "finish"
proc finish {} {
    global ns tf e
    foreach i {0 1} {
        puts [format "node %d used %.4f J in 200 s" $i [expr $e(start,$i) - $e(end,$i)]]
    }
    $ns flush-trace
    close $tf
    exit 0
}
$ns run

Four lines deserve a word.

Mac/SMAC set syncFlag_ 1. Without it S-MAC never sleeps. ns-default.tcl sets syncFlag_ to 1 at line 737 and back to 0 at line 807, and the later setting wins; with it at 0, S-MAC ignores dutyCycle_ altogether.

munotes.in188

Practical 17: An Energy-Aware Duty-Cycling MAC for Sensor Networks

Readings start at 50 s. S-MAC nodes spend their first SYNC period, ten cycles, listening and exchanging SYNC packets before they settle on a schedule; at 5 per cent that is 28.7 s. Unicast sent before then fails. NS-2's own S-MAC test suite starts its synchronised tests' traffic at 40 s for the same reason.

The energy is read at 50.1 s and 250.1 s, 0.1 s after a reading, and the difference is what each node used in those 200 s. The reason is how ns-2 charges energy: at radio events, not continuously. An 802.11 radio that has heard nothing for ten seconds has not yet been charged for those ten seconds of listening, and a reading taken then would be too high. Just after a reading has been exchanged, every radio's account is up to date.

One sensor, not six. With six sensors sharing one sink, NS-2's S-MAC lost most of the readings in the queue to address resolution (drop reason ARP) at the low duty cycles, while 802.11 delivered them all. NS-2's own S-MAC unicast tests use one pair of nodes, and so does this practical.

report.awk, from Practical 16, counts the readings delivered and their delay. runs.sh runs every case. S-MAC prints lines of its own on the terminal (node: 1 ....data sent Uni....), so runs.sh keeps only the energy lines.

# report.awk: readings delivered to the sink, and their average delay
$1 == "s" && $4 == "AGT" { sent++; t[$6] = $2 }
$1 == "r" && $4 == "AGT" { got++; delay += $2 - t[$6] }
END { printf "readings delivered %d of %d, average delay %.1f ms\n", got, sent, delay / got * 1000 }
# runs.sh: 802.11, then S-MAC at four duty cycles: energy, delivery and delay
for d in 802.11 50 20 10 5; do
    echo "== $d: $(ns smac.tcl $d 2> /dev/null | grep ' used ' | xargs)"
    awk -f report.awk smac.tr
done
$ bash runs.sh
== 802.11: node 0 used 11.8197 J in 200 s node 1 used 11.8196 J in 200 s
readings delivered 21 of 21, average delay 2.2 ms
== 50: node 0 used 6.3109 J in 200 s node 1 used 6.3149 J in 200 s
readings delivered 21 of 21, average delay 266.7 ms
== 20: node 0 used 3.3411 J in 200 s node 1 used 3.3366 J in 200 s
readings delivered 21 of 21, average delay 524.6 ms
== 10: node 0 used 2.8586 J in 200 s node 1 used 2.8541 J in 200 s
readings delivered 21 of 21, average delay 768.5 ms
== 5: node 0 used 4.9646 J in 200 s node 1 used 5.1210 J in 200 s
readings delivered 21 of 21, average delay 1599.1 ms
munotes.in189

Practical 17: An Energy-Aware Duty-Cycling MAC for Sensor Networks

802.11 listened for all 200 s. 0.0591 W for 200 s is 11.82 J, what each radio used: it never slept, and the 21 readings themselves cost almost nothing.

S-MAC saved most of it, and every reading still arrived. At 10 per cent each radio used 2.86 J, a quarter of 802.11's. The delay grew as dutycycle.py predicts, from 267 ms at 50 per cent to 1.6 s at 5 per cent.

But the savings are not in proportion, and 5 per cent used more than 10. At 10 per cent the radios used 24 per cent of 802.11's energy, not 10; at 5 per cent, about 43 per cent. The ideal model cannot explain that. The radio's own schedule can.

Step 3: the radio's schedule, read from the energy log

NS-2 writes a line beginning N into the trace every time a radio wakes from sleep, with the energy it has left (WirelessPhy::node_wakeup). Between two such lines, the energy used, at the listening power, is how long the radio was on. wakes.awk prints that for node 1:

# wakes.awk: node 1's radio from ns-2's energy log. An N line is written at every wake-up, so
# the energy used between two of them, at the listening power 0.0591 W, is time the radio was on.
$1 == "N" && $5 == 1 {
    if (seen && $3 > from && $3 < to)
        printf "%8.3f to %8.3f s   %6.3f s long, radio on for %6.3f s of it\n", t, $3, $3 - t, (e - $7) / 0.0591
    t = $3; e = $7; seen = 1
}

At 10 per cent, twelve seconds of it, including the readings made at 100 s and 110 s:

$ ns smac.tcl 10 > /dev/null 2>&1
$ awk -v from=100 -v to=112 -f wakes.awk smac.tr
  99.020 to  100.310 s    1.290 s long, radio on for  0.000 s of it
 100.310 to  100.448 s    0.138 s long, radio on for  0.148 s of it
 100.448 to  100.502 s    0.054 s long, radio on for  0.049 s of it
 100.502 to  101.886 s    1.384 s long, radio on for  1.373 s of it
 101.886 to  103.176 s    1.290 s long, radio on for  0.000 s of it
 103.176 to  103.319 s    0.143 s long, radio on for  0.143 s of it
 103.319 to  104.609 s    1.290 s long, radio on for  0.000 s of it
 104.609 to  104.630 s    0.021 s long, radio on for  0.024 s of it
 104.630 to  104.752 s    0.122 s long, radio on for  0.119 s of it
 104.752 to  106.042 s    1.290 s long, radio on for  0.000 s of it
 106.042 to  106.185 s    0.143 s long, radio on for  0.143 s of it
 106.185 to  107.475 s    1.290 s long, radio on for  0.000 s of it
 107.475 to  107.618 s    0.143 s long, radio on for  0.143 s of it
 107.618 to  108.908 s    1.290 s long, radio on for  0.000 s of it
 108.908 to  109.051 s    0.143 s long, radio on for  0.143 s of it
 109.051 to  110.341 s    1.290 s long, radio on for  0.000 s of it
 110.341 to  110.433 s    0.092 s long, radio on for  0.102 s of it
 110.433 to  110.487 s    0.054 s long, radio on for  0.049 s of it
 110.487 to  111.917 s    1.430 s long, radio on for  1.419 s of it
munotes.in190

Practical 17: An Energy-Aware Duty-Cycling MAC for Sensor Networks

(The small differences between a stretch's length and the time on come from how ns-2 charges a packet's reception; they do not change the picture.)

The schedule is exactly the one worked out from the source. On for 0.143 s, the 143.2 ms listen period; then off for 1.290 s, the 1289.8 ms of sleep; a cycle of 1.433 s.

And after each reading, the radio stayed on for a whole extra cycle. The reading made at 100 s was exchanged in the listen period that began at 100.310 s, and from 100.502 s the radio stayed on for 1.373 s before sleeping again; the same after the reading at 110 s. Adaptive listening, which would do something like this on purpose, is not compiled into this S-MAC (//#define JOURNAL_PAPER, smac.h line 85); whatever the cause, the cost is measured, and it is about one cycle per reading.

That gives a rule of thumb: the radio is on for its listen share of the time, plus about one whole cycle for each reading. And a cycle grows as the duty cycle falls, so at low duty cycles the extra cycle per reading costs more than the sleep saves. predict.py applies the rule to the 200 s window, which holds 20 exchanges:

# predict.py: the energy of the 200 s window if the radio is on for its listen share, plus one
# whole cycle for each of the 20 readings exchanged in it, at the listening power
LISTEN, POWER, WINDOW, READINGS = 0.1432, 0.0591, 200.0, 20
for duty in (0.5, 0.2, 0.1, 0.05):
    cycle = LISTEN / duty + 0.001          # smac.cc adds one 1 ms tick
    on = duty * WINDOW + READINGS * cycle
    print('%3.0f %%: on %6.2f s of the 200, so %.3f J' % (duty * 100, on, on * POWER))
munotes.in191

Practical 17: An Energy-Aware Duty-Cycling MAC for Sensor Networks

$ python3 predict.py
 50 %: on 105.75 s of the 200, so 6.250 J
 20 %: on  54.34 s of the 200, so 3.211 J
 10 %: on  48.66 s of the 200, so 2.876 J
  5 %: on  67.30 s of the 200, so 3.977 J

At 50, 20 and 10 per cent the rule is within 4 per cent of the measured energy. At 5 per cent it is 1 J short, and the log shows the rest:

$ ns smac.tcl 5 > /dev/null 2>&1
$ awk -v from=30 -v to=66 -f wakes.awk smac.tr
  37.283 to   54.533 s   17.250 s long, radio on for 17.253 s of it
  54.533 to   54.555 s    0.022 s long, radio on for  0.053 s of it
  54.555 to   57.380 s    2.825 s long, radio on for  2.790 s of it
  57.380 to   57.434 s    0.054 s long, radio on for  0.049 s of it
  57.434 to   60.271 s    2.837 s long, radio on for  2.836 s of it
  60.271 to   60.325 s    0.054 s long, radio on for  0.049 s of it
  60.325 to   65.917 s    5.592 s long, radio on for  5.584 s of it

From 37.3 s to 65.9 s the radio was on almost without a break: 28.6 s, which is one SYNC period, ten cycles of 2.865 s. That is S-MAC's neighbour discovery, listening "for a whole period of the SYNCPERIOD" (smac.h). About 16 s of it fell inside the measuring window, 0.95 J at the listening power, which is the missing joule. The longer the cycle, the longer each neighbour discovery lasts, so it too costs most at the lowest duty cycles.

So the lowest duty cycle is not always the most economical. For this traffic, a reading every 10 s, NS-2's S-MAC used the least energy at 10 per cent. A sensor network's duty cycle has to be chosen for its traffic as well as its battery.

Procedure

  1. Write dutycycle.py: an ideal duty-cycled MAC with S-MAC's listen period; compute energy a day, battery life and delay over one and five hops for several duty cycles.
  2. Work out NS-2 S-MAC's listen period and cycle from its source.
  3. Write smac.tcl: one sensor and a sink, MICAz radio powers, S-MAC with syncFlag_ 1, readings from 50 s, energy read at 50.1 s and 250.1 s.
  4. Run it under 802.11 and at duty cycles of 50, 20, 10 and 5 per cent; record energy, delivery and delay.
  5. Read the radio's on and off periods from the energy log with wakes.awk, at 10 and at 5 per cent.
  6. Test the rule of thumb with predict.py, and account for the difference at 5 per cent.
munotes.in192

Practical 17: An Energy-Aware Duty-Cycling MAC for Sensor Networks

Observations

Duty cycleEnergy in 200 s, sensorShare of 802.11'sAverage delayRule of thumb
802.11, always on11.8196 J100 %2.2 ms
S-MAC, 50 %6.3149 J53 %266.7 ms6.250 J
S-MAC, 20 %3.3366 J28 %524.6 ms3.211 J
S-MAC, 10 %2.8541 J24 %768.5 ms2.876 J
S-MAC, 5 %5.1210 J43 %1599.1 ms3.977 J, plus a neighbour discovery
Measured from the energy log, 10 %Value
Radio on in each cycle0.143 s
Radio off in each cycle1.290 s
Extra time on after each readingabout 1.4 s

Result

An ideal duty-cycling MAC with S-MAC's listen period showed energy falling in proportion to the duty cycle, from 2553 J a day at 50 per cent to 51 J at 1 per cent, and delay rising in the same proportion, to 7.1 s a hop at 1 per cent. In NS-2, S-MAC with its sleep schedule switched on delivered all 21 readings at every duty cycle, and used 53, 28, 24 and 43 per cent of an always-listening 802.11 radio's 11.82 J at 50, 20, 10 and 5 per cent, with delays of 0.27 to 1.60 s against 802.11's 2.2 ms. The energy log showed the radio's schedule exactly as S-MAC's source defines it, 0.143 s on and 1.290 s off at 10 per cent, and showed two extra costs that grow as the duty cycle falls: about one whole cycle awake after each reading, and neighbour discovery, a whole SYNC period awake. With them, the radio used least energy at 10 per cent, not at 5.

Where marks are lost

Forgetting syncFlag_ 1. ns-default.tcl leaves S-MAC's sleep schedule off, and dutyCycle_ then does nothing: every duty cycle gives the same energy.

Sending before the schedules settle. S-MAC nodes spend their first SYNC period finding each other. Start the traffic after it.

Reading energy at an arbitrary time. ns-2 charges energy at radio events, so a reading taken long after the last one is out of date. Read it just after traffic, or over the whole run.

Assuming energy falls in proportion to the duty cycle. In the ideal model it does; in S-MAC every reading and every neighbour discovery keeps the radio awake for a time that grows with the cycle. Measure it.

Quoting delay per hop for a multi-hop network. A duty-cycled packet waits at every hop. Five hops at 10 per cent is 6.4 s in the ideal model.

Mistaking S-MAC's printed lines for errors. node: 1 ....data sent Uni.... is S-MAC's own debugging output on the terminal, not a problem.

munotes.in193

Practical 17: An Energy-Aware Duty-Cycling MAC for Sensor Networks

For the journal

Write: aim; idle listening and duty cycling; S-MAC's schedule, SYNC and neighbour discovery; the listen period and cycle worked out from smac.cc; dutycycle.py and its table; smac.tcl, with the four points about it; the results of runs.sh; the energy log at 10 and 5 per cent with wakes.awk; the rule of thumb and predict.py; observations; result.

Quick revision

  • Idle listening costs as much as receiving; duty cycling puts the radio to sleep between listen periods.
  • Duty cycle = time awake ÷ cycle. Energy falls with it; delay rises with it, at every hop.
  • S-MAC: periodic listen and sleep, SYNC packets to share schedules, RTS/CTS/ACK inside the listen period, sleep while neighbours exchange.
  • NS-2's S-MAC: listen 143.2 ms; cycle = listen × 100 ÷ duty cycle + 1 ms; 1433 ms at 10 per cent.
  • Set Mac/SMAC set syncFlag_ 1, or S-MAC never sleeps.
  • Start traffic after the first SYNC period.
  • Measured: 802.11 11.82 J in 200 s; S-MAC 10 per cent 2.85 J, delay 0.77 s.
  • Each reading kept the radio on about one extra cycle, and neighbour discovery a whole SYNC period: both cost more at low duty cycles.
  • The best duty cycle depends on the traffic.

Questions you must be able to answer

1. What is idle listening, and why does it matter so much in a sensor network? A radio listening for packets that may not come. On a MICAz it draws the full receive current, 19.7 mA, so a radio that listens all the time uses almost all of a node's energy, even when little is sent.

2. What is a duty cycle? What does a 10 per cent duty cycle mean for S-MAC? The fraction of time the radio is awake. At 10 per cent, S-MAC's radio listens for 143.2 ms and then sleeps, in cycles of 1.433 s.

3. How does S-MAC keep neighbouring nodes awake at the same time? By SYNC packets. A node that has chosen a schedule announces it in a SYNC packet, and a node that hears one before choosing its own follows the neighbour's schedule, so neighbours listen together.

4. Why does a lower duty cycle increase delay, and why is it worse over several hops? A packet can only cross a hop while the receiver is listening, so it waits for the next listen period, on average half a cycle, and the cycle grows as the duty cycle falls. The wait happens again at every hop.

5. What does Mac/SMAC set syncFlag_ 1 do, and what happens without it? It switches on S-MAC's periodic sleep. ns-default.tcl sets it back to 0 after setting it to 1, so without the line S-MAC never sleeps and the duty cycle has no effect.

munotes.in194

Practical 17: An Energy-Aware Duty-Cycling MAC for Sensor Networks

6. Why do the readings in smac.tcl start at 50 s? Because S-MAC nodes spend their first SYNC period, ten cycles, discovering each other's schedules, and unicast before then fails. At 5 per cent that period is 28.7 s.

7. Why is the energy read at 50.1 s and 250.1 s? Because ns-2 charges a radio's energy when its state changes. Just after a reading has been exchanged every radio's account is up to date; a reading taken during a long quiet spell would leave out the listening since the last event.

8. What did the energy log show at 10 per cent? The radio on for 0.143 s and off for 1.290 s in each cycle, as S-MAC's source defines it, and on for about one whole extra cycle after each reading was exchanged.

9. Why did S-MAC use more energy at 5 per cent than at 10 per cent here? Two costs grow with the cycle: about one whole cycle awake after each reading, 2.9 s at 5 per cent, and neighbour discovery, a whole SYNC period awake, 28.6 s. At 5 per cent they outweighed the extra sleep.

10. How much energy did S-MAC at 10 per cent save against 802.11, and at what cost? It used 2.85 J in 200 s against 11.82 J, about a quarter, and every reading still arrived; the cost was delay, 768.5 ms against 2.2 ms.

11. Two AA cells hold 3 Ah at 3 V. How long would they power a radio at a 1 per cent duty cycle, by dutycycle.py? 631.3 days, against 6.3 days for a radio listening all the time.

12. How would you choose a duty cycle for a real deployment? From the traffic and the delay it can tolerate as well as the battery: measure the energy at several duty cycles with the real traffic, since the costs of each exchange and of neighbour discovery grow with the cycle, and pick the lowest-energy one whose delay, over the network's number of hops, is acceptable.

Contents This chapter on its own page

munotes.in195

Chapter Twenty-One

Practical 18: A MANET with Directional Antennas

Syllabus topic Module 2, "MANET Simulation with Directional Antenna: Simulate MANET using directional antennas and analyze improvements in spatial reuse and interference reduction."

Aim

To simulate a MANET whose nodes use directional antennas of different beamwidths, and to measure how much they reduce interference and increase spatial reuse compared with omnidirectional antennas.

What you need to know before you start

An omnidirectional antenna radiates equally in every direction. Every MANET so far in this book has used one: NS-2's Antenna/OmniAntenna. A transmission from an omnidirectional node reaches every node within its range, whether it is the intended receiver or not, and every one of them is disturbed: it cannot receive anything else while the transmission lasts.

A directional antenna concentrates its power in a beam, a sector of a certain width, the beamwidth, pointed at the receiver. A node outside the beam hardly hears the transmission. A node can also listen directionally, pointing its beam at the node it expects to hear, and then it hardly hears anything from other directions.

Two measures of the gain.

  • Interference: how many other nodes one transmission disturbs. An omnidirectional transmission covers a full circle; a beam of θ degrees covers about θ/360 of it.
  • Spatial reuse: how many transmissions can go on at the same time in the same area without spoiling each other. With less interference, more links can share the channel at once, and the network carries more.

Step 1: why this practical is not in NS-2

Ask the running simulator which antennas it has:

$ printf 'puts [Antenna info subclass]\n' > antennas.tcl
$ ns antennas.tcl
Antenna/OmniAntenna

Antenna info subclass lists every class that inherits from NS-2's Antenna, and there is exactly one. NS-2.35 as Ubuntu packages it has no directional antenna, so a directional MANET cannot be simulated in it without writing C++ inside the simulator. This practical builds the simulation in Python instead, using NS-2's own 250 m range so that its numbers can be set beside the rest of the book.

Step 2: the simulation

directional.py places 40 nodes at random in a 1000 m square and gives each one a link to its nearest neighbour within 250 m. It decides whether two links can transmit at the same time with a simple and standard rule, the protocol model of interference:

  • a node does one thing at a time: it cannot send on two links, or send and receive at once;
  • a transmission spoils another link's reception if the transmitter is within 250 m of that link's receiver, its beam reaches the receiver, and the receiver's own beam, pointed at its sender, takes it in.

With a 360 degree beam, every node in range is reached and every receiver hears everything: the omnidirectional case. Each narrower beam is the same network with only the antennas changed. The last row, a beam of 0 degrees, is a check: no transmission reaches anyone but its own receiver, so only the rule that a node does one thing at a time is left, and the row shows how far the network could go with no interference at all.

munotes.in196

Practical 18: A MANET with Directional Antennas

# directional.py: interference and spatial reuse in a MANET, omnidirectional against directional
# antennas of narrower and narrower beams, averaged over ten random networks
import math
import random

N, SIDE, RANGE, NETWORKS = 40, 1000.0, 250.0, 10   # 40 nodes in 1000 m by 1000 m; NS-2's 250 m range
pos = []


def dist(a, b):
    return math.dist(pos[a], pos[b])


def in_beam(src, aim, target, beam):
    """Is `target` inside the beam, `beam` degrees wide, that `src` points at `aim`?"""
    if beam >= 360:
        return True
    ang = lambda b: math.atan2(pos[b][1] - pos[src][1], pos[b][0] - pos[src][0])
    off = abs((ang(target) - ang(aim) + math.pi) % (2 * math.pi) - math.pi)
    return math.degrees(off) <= beam / 2


def nearest_links():
    """Every node sends to its nearest neighbour within range."""
    links = []
    for a in range(N):
        near = [b for b in range(N) if b != a and dist(a, b) <= RANGE]
        if near:
            links.append((a, min(near, key=lambda b: dist(a, b))))
    return links


def spoils(l1, l2, beam):
    """Does link l1 transmitting spoil link l2's reception? Both ends point their beams."""
    (t1, r1), (t2, r2) = l1, l2
    if len({t1, r1} & {t2, r2}):                  # a node does one thing at a time
        return True
    return (dist(t1, r2) <= RANGE and in_beam(t1, r1, r2, beam)    # t1's beam reaches r2
            and in_beam(r2, t2, t1, beam))                         # and r2 is listening that way


def conflict(l1, l2, beam):
    return spoils(l1, l2, beam) or spoils(l2, l1, beam)


def together(links, beam):
    """How many links a scheduler can let transmit at once: greedy, in link order."""
    on = []
    for l in links:
        if not any(conflict(l, o, beam) for o in on):
            on.append(l)
    return len(on)


def random_access(links, beam, p=0.3, slots=1000, seed=2):
    """Each link transmits in a slot with probability p; how many succeed per slot?"""
    r = random.Random(seed)
    ok = 0
    for _ in range(slots):
        active = [l for l in links if r.random() < p]
        ok += sum(1 for l in active if not any(conflict(l, o, beam) for o in active if o != l))
    return ok / slots


def disturbed(links, beam):
    """On average, how many other nodes lie inside one transmission's beam and range?"""
    return sum(sum(1 for n in range(N) if n not in (t, r) and dist(t, n) <= RANGE
                   and in_beam(t, r, n, beam)) for t, r in links) / len(links)


beams = (360, 180, 90, 60, 30, 0)             # 0: no interference at all, only busy nodes
total = {b: [0.0, 0.0, 0.0] for b in beams}
for net in range(1, NETWORKS + 1):
    rng = random.Random(net)
    pos[:] = [(rng.uniform(0, SIDE), rng.uniform(0, SIDE)) for _ in range(N)]
    links = nearest_links()
    for b in beams:
        total[b][0] += disturbed(links, b) / NETWORKS
        total[b][1] += together(links, b) / NETWORKS
        total[b][2] += random_access(links, b) / NETWORKS

print('averages over %d random networks of %d nodes, each node sending to its nearest neighbour\n' % (NETWORKS, N))
print('beam     nodes disturbed   links at once   random access: successes per slot')
for b in beams:
    name = 'omni' if b == 360 else ('none' if b == 0 else '%d deg' % b)
    print('%-7s  %15.2f  %14.1f  %34.2f' % (name, *total[b]))
munotes.in197

Practical 18: A MANET with Directional Antennas

It measures three things for each beamwidth, averaged over ten random networks:

ColumnWhat it counts
nodes disturbedother nodes inside one transmission's beam and range
links at oncehow many links a scheduler can let transmit together, choosing them greedily
random accesswith every link transmitting in a slot at random, three times in ten, how many transmissions succeed per slot
$ python3 directional.py
averages over 10 random networks of 40 nodes, each node sending to its nearest neighbour

beam     nodes disturbed   links at once   random access: successes per slot
omni                4.95             8.0                                1.31
180 deg             2.60            13.5                                3.92
90 deg              1.34            15.3                                5.68
60 deg              0.88            15.5                                5.99
30 deg              0.45            15.8                                6.08
none                0.00            15.8                                6.14

Interference falls with the beam's share of the circle. An omnidirectional transmission disturbed 4.95 other nodes on average. A beam covers θ/360 of the circle, so it should disturb about that share: 4.95 / 2 = 2.475 at 180 degrees, 4.95 / 4 = 1.2375 at 90, 4.95 / 6 = 0.825 at 60, 4.95 / 12 = 0.4125 at 30. The measured values, 2.60, 1.34, 0.88 and 0.45, are close to those, each a little above.

Spatial reuse grows as interference falls. With omnidirectional antennas a scheduler could run 8 of the 40 links at once. With a 180 degree beam, 13.5; with 90 degrees, 15.3. Narrowing the beam to half a circle nearly doubled the number of transmissions the same area could carry at the same moment.

Random access gains even more. When links transmit at random, as in ALOHA or CSMA without perfect scheduling, omnidirectional antennas let only 1.31 transmissions succeed per slot: most attempts spoil each other. A 90 degree beam lets 5.68 succeed, over four times as many, because a transmission now spoils only the few receivers inside its beam that are also listening its way.

And the gain stops where interference stops. From 60 degrees down, the numbers hardly move, and at 30 degrees they are almost those of the last row, 15.8 links and 6.14 successes, where there is no interference at all. What limits the network there is not the antennas but the nodes: a node in a link cannot be in another at the same time, and the 40 links are among only 40 nodes, many of them pairs that are each other's nearest neighbour.

munotes.in198

Practical 18: A MANET with Directional Antennas

Procedure

  1. Ask NS-2 which antenna classes it has, and show that it has only the omnidirectional one.
  2. Write directional.py: random nodes, a link from each to its nearest neighbour, the protocol model of interference with a beam of any width at both ends.
  3. For omnidirectional antennas and beams of 180, 90, 60 and 30 degrees, measure the nodes each transmission disturbs, the links that can transmit at once, and the successes per slot under random access, averaged over ten networks.
  4. Add the case of no interference at all, as a ceiling.
  5. Compare the disturbed nodes with the beam's share of the circle, and explain where the gain in spatial reuse stops.

Observations

BeamwidthNodes disturbed per transmissionLinks at onceRandom access, successes per slot
omnidirectional4.958.01.31
180 degrees2.6013.53.92
90 degrees1.3415.35.68
60 degrees0.8815.55.99
30 degrees0.4515.86.08
no interference, ceiling0.0015.86.14

Result

NS-2.35 was shown to have only an omnidirectional antenna, and a MANET with directional antennas was simulated in Python on ten random 40-node networks with NS-2's 250 m range. Narrowing the beam reduced interference in proportion to the beam's share of the circle: one transmission disturbed 4.95 other nodes with omnidirectional antennas, 1.34 with a 90 degree beam and 0.45 with a 30 degree one. Spatial reuse rose accordingly: the links that could transmit at once went from 8.0 to 15.3 at 90 degrees, and under random access the successful transmissions per slot from 1.31 to 5.68, over four times as many. Below about 60 degrees the gains levelled off at the network's ceiling without any interference, 15.8 links and 6.14 successes per slot, where the limit is that each node can take part in one transmission at a time.

Where marks are lost

Trying to find a directional antenna in NS-2. Ubuntu's NS-2.35 has only Antenna/OmniAntenna. Say so, show it, and simulate directionality another way.

Comparing different networks. Change only the antennas: the same nodes, the same links, the same random numbers.

Expecting the gain to grow without limit as the beam narrows. Past the point where interference is gone, the nodes themselves are the limit. Show the ceiling.

Pointing only the transmitter's beam. A receiver listening in every direction still hears every transmitter in range. The gain needs the receiver's beam too, which is why the model points both.

munotes.in199

Practical 18: A MANET with Directional Antennas

Forgetting what the model assumes. It assumes each node knows where its neighbour is and points exactly at it. A real directional network must find its neighbours first, and a node listening in one direction cannot hear a node calling from another.

For the journal

Write: aim; omnidirectional and directional antennas; interference and spatial reuse; why NS-2 cannot simulate this, with the check; the protocol model of interference; directional.py; the table it prints; the check of the disturbed nodes against the beam's share of the circle; where the gain stops, and why; what the model assumes; observations; result.

Quick revision

  • Omnidirectional: equal power in every direction; disturbs every node in range.
  • Directional: a beam of some width pointed at the receiver; disturbs only nodes in the beam.
  • A beam of θ degrees covers about θ/360 of the circle, and disturbs about that share of the nodes.
  • Spatial reuse: more transmissions at once in the same area.
  • NS-2.35 has only Antenna/OmniAntenna; Antenna info subclass shows it.
  • Protocol model: a reception is spoiled by a transmitter in range whose beam reaches the receiver while the receiver listens its way; a node does one thing at a time.
  • Measured: disturbed 4.95 to 0.45; links at once 8.0 to 15.8; random-access successes 1.31 to 6.08.
  • Gains stop when interference stops: then each node's single radio is the limit.

Questions you must be able to answer

1. What is the difference between an omnidirectional and a directional antenna? An omnidirectional antenna radiates equally in every direction, so its transmission reaches every node within range. A directional antenna concentrates its power in a beam of a certain width pointed at one direction, so nodes outside the beam hardly hear it.

2. What is spatial reuse? Several transmissions using the channel at the same time in the same area without spoiling each other. The less each transmission interferes, the more of them can go on at once.

3. Why could this practical not be done in NS-2 as installed? Because NS-2.35 has only one antenna class, Antenna/OmniAntenna, as Antenna info subclass shows. A directional antenna would have to be written into the simulator in C++.

4. How does the protocol model decide whether one transmission spoils another link's reception? If a node is involved in both, or if the transmitter is within 250 m of the other link's receiver, its beam reaches that receiver, and the receiver's beam, pointed at its own sender, takes the transmitter in.

5. A beam is 90 degrees wide. What share of the omnidirectional interference would you expect, and what was measured? About a quarter, since 90 degrees is a quarter of the circle: 4.95 / 4 = 1.2375 nodes. The measured value was 1.34.

munotes.in200

Practical 18: A MANET with Directional Antennas

6. Narrow beams doubled the links a scheduler could run at once, but multiplied random access's successes more than four times. Why the difference? A scheduler already avoids letting conflicting links transmit together, so beams help it only by leaving fewer conflicts to avoid. Under random access, conflicting transmissions happen and spoil each other; narrow beams remove most of those conflicts, so far more of the attempts succeed.

7. How much did a 90 degree beam improve random access over omnidirectional antennas? From 1.31 successful transmissions per slot to 5.68, over four times as many, because each transmission spoiled only the few receivers inside its beam that were listening its way.

8. Why did the links that can transmit at once stop growing below about 60 degrees? Because interference was almost gone: the ceiling with no interference at all is 15.8 links, which 30 degrees already reached. The limit is then the nodes: each can be in only one transmission at a time.

9. Why must the receiver point its beam as well as the transmitter? A receiver listening in every direction hears every transmitter within range, whichever way the transmitters point. Only a receiver listening towards its own sender ignores the others.

10. What does a real directional network have to do that this model assumes away? Find out where its neighbours are before it can point at them, and cope with a node that is listening in one direction when another node calls it from a different one.

Contents This chapter on its own page

munotes.in201

Chapter Twenty-Two

Practical 19: Sensor Network Deployment and Coverage Analysis

Syllabus topic Module 2, "Wireless Sensor Network Deployment and Coverage Analysis: Simulate random and grid deployment of sensor nodes and evaluate network coverage and connectivity."

Aim

To simulate grid and random deployments of sensor nodes in a field, and to evaluate how much of the field each covers with its sensing range and how many sensors each can connect to the sink over radio links.

What you need to know before you start

Two ranges. A sensor has a sensing range, how far away it can detect an event, and a radio range, how far its packets reach. They are different: a temperature sensor senses the air a few metres around it but its radio reaches hundreds of metres. This practical uses a sensing radius of 100 m and NS-2's radio range of 250 m.

Coverage is the fraction of the field within sensing range of at least one sensor. An event in an uncovered spot is never seen. Connectivity is whether each sensor has a chain of radio links, each within radio range, to the sink that collects the readings. A sensor that is not connected sees events and cannot report them.

Grid deployment places sensors in a regular square pattern, as a planned installation would. The weakest spot of a square grid is the centre of each square, equally far from its four corners: half the square's diagonal, the spacing divided by the square root of 2, 1.414. So a grid covers everything if its spacing is at most 1.414 × the sensing radius: 141.4 m for a 100 m radius.

Random deployment scatters sensors, as dropping them from the air would. Some spots end up covered many times over and others not at all. For sensors scattered at random with density λ per square metre, the fraction of a large field left uncovered is about e^(-λπR²), so the coverage is about 1 - e^(-λπR²), for sensing radius R.

Step 1: the simulation

coverage.py works on a 1000 m square field with the sink at its centre. It measures coverage on 40,000 points 5 m apart, and connectivity by following radio links outwards from the sink. For random deployment it averages ten seeded deployments.

# coverage.py: grid against random deployment of sensors: how much of the field they sense, and
# how many of them can reach the sink at the centre over radio links
import math
import random

SIDE = 1000.0            # a 1000 m by 1000 m field
SENSE = 100.0            # sensing radius, m
STEP = 5.0               # coverage is measured on points 5 m apart
SINK = (500.0, 500.0)
POINTS = [(x * STEP + STEP / 2, y * STEP + STEP / 2)
          for x in range(int(SIDE / STEP)) for y in range(int(SIDE / STEP))]


def grid(k):
    s = SIDE / k
    return [(s / 2 + i * s, s / 2 + j * s) for i in range(k) for j in range(k)]


def scattered(n, seed):
    r = random.Random(seed)
    return [(r.uniform(0, SIDE), r.uniform(0, SIDE)) for _ in range(n)]


def uncovered(nodes):
    """How many of the 40000 measuring points no sensor is within sensing range of."""
    return sum(1 for p in POINTS if not any(math.dist(p, s) <= SENSE for s in nodes))


def covered(nodes):
    return 1 - uncovered(nodes) / len(POINTS)


def reach_sink(nodes, radio):
    """How many sensors have a chain of radio links, each at most `radio` long, to the sink."""
    everyone = [SINK] + nodes
    seen, todo = {0}, [0]
    while todo:
        a = todo.pop()
        for b in range(len(everyone)):
            if b not in seen and math.dist(everyone[a], everyone[b]) <= radio:
                seen.add(b)
                todo.append(b)
    return len(seen) - 1


for k in (8, 7, 6):
    u = uncovered(grid(k))
    print('grid %d x %d = %d sensors, %.1f m apart: %5d of %d points uncovered, covered %.2f %%'
          % (k, k, k * k, SIDE / k, u, len(POINTS), 100 * (1 - u / len(POINTS))))
print('   a grid covers everything if its spacing is at most sqrt(2) x %.0f m = %.1f m' % (SENSE, math.sqrt(2) * SENSE))
for n in (64, 147):
    cov = [covered(scattered(n, seed)) for seed in range(1, 11)]
    print('random, %d sensors, ten deployments: covered %.1f %% on average (%.1f to %.1f)'
          % (n, 100 * sum(cov) / 10, 100 * min(cov), 100 * max(cov)))
    density = n / SIDE ** 2
    print('   formula 1 - e^(-density x pi x R^2): %.1f %%' % (100 * (1 - math.exp(-density * math.pi * SENSE ** 2))))
for radio in (250, 130):
    reach = [reach_sink(scattered(64, seed), radio) for seed in range(1, 11)]
    print('radio range %d m: grid sensors reaching the sink %d of 64; random %.1f of 64 on average (fewest %d)'
          % (radio, reach_sink(grid(8), radio), sum(reach) / 10, min(reach)))
print('random deployment 1, positions in m:')
r1 = scattered(64, 1)
for i in range(0, 64, 8):
    print('  ' + ' '.join('%3.0f,%3.0f' % p for p in r1[i:i + 8]))
munotes.in202

Practical 19: Sensor Network Deployment and Coverage Analysis

$ python3 coverage.py
grid 8 x 8 = 64 sensors, 125.0 m apart:     0 of 40000 points uncovered, covered 100.00 %
grid 7 x 7 = 49 sensors, 142.9 m apart:    12 of 40000 points uncovered, covered 99.97 %
grid 6 x 6 = 36 sensors, 166.7 m apart:  1984 of 40000 points uncovered, covered 95.04 %
   a grid covers everything if its spacing is at most sqrt(2) x 100 m = 141.4 m
random, 64 sensors, ten deployments: covered 83.8 % on average (79.8 to 88.3)
   formula 1 - e^(-density x pi x R^2): 86.6 %
random, 147 sensors, ten deployments: covered 98.3 % on average (97.1 to 99.1)
   formula 1 - e^(-density x pi x R^2): 99.0 %
radio range 250 m: grid sensors reaching the sink 64 of 64; random 63.8 of 64 on average (fewest 62)
radio range 130 m: grid sensors reaching the sink 64 of 64; random 23.0 of 64 on average (fewest 6)
random deployment 1, positions in m:
  134,847 764,255 495,449 652,789  94, 28 836,433 762,  2 445,722
  229,945 901, 31  25,541 939,381 217,422  29,222 438,496 233,231
  219,460 290, 21 838,556 642,186 993,860 121,333 721,711 936,422
  830,670 303,588 882,846 505,589  35,243 797,414 173,549 703,674
  375,439 508,778 521,393 490, 30  43,703 983,593 394,170 502,982
  771,540 860,232 514,952 578,459 269,548 957,  6 784,820 886,741
  809,519 561,426  56,870 570,200 505,485 357,346 538,623 612,458
   28,230 177,584 861,798 797,816 255,842 673, 83  17, 15 756,250
munotes.in203

Practical 19: Sensor Network Deployment and Coverage Analysis

Two square fields 1000 metres on a side, each with 64 sensors drawn as dots inside shaded disks of 100 metres radius, and the sink as a small square at the centre. Left, the 8 by 8 grid: the disks overlap in a regular pattern and no white shows. Right, random deployment 1: the disks crowd together in places and leave white gaps in others, most of all near the edges.

Figure 22.1 The same 64 sensors, in a grid and scattered at random

The grid rule holds exactly at its threshold. At 125 m spacing, below the 141.4 m limit, not one of the 40,000 points is uncovered. At 142.9 m, just above it, 12 points are: the centre of each square of four sensors is half a diagonal from all four, 142.9 m divided by the square root of 2, which is 101.0 m to one decimal place, just beyond the 100 m radius, and leaves a hole a few metres across. At one decimal place those holes would have read as 100.0 per cent, which is why the program counts points. At 166.7 m the holes are large: 1984 points, nearly 5 per cent of the field.

Random deployment wastes sensors. The same 64 sensors covered only 83.8 per cent of the field on average, and as little as 79.8 per cent: where sensors fall close together they sense the same ground twice, and elsewhere they leave gaps. To reach about 99 per cent at random takes 147 sensors, more than twice the grid's 64 for complete coverage.

The formula is close, and a little high. For 64 sensors it predicts 86.6 per cent against 83.8 measured, and for 147, 99.0 against 98.3. The formula assumes a field without edges; in a real one, a sensor near the edge senses partly outside the field, where coverage counts for nothing, so the measured coverage is lower. The figure shows it: the white gaps gather at the edges.

Connectivity follows the same pattern, more sharply. With NS-2's 250 m radio range, almost every sensor could reach the sink either way: all 64 in the grid, 63.8 on average when scattered. With a 130 m radio range, just over the grid's 125 m spacing, the grid still connected all 64, because every sensor has neighbours exactly 125 m away in four directions; the scattered sensors connected only 23.0 on average, and in the worst deployment 6. A random field needs radio ranges well above its average spacing to stay connected; a grid needs only its spacing.

munotes.in204

Practical 19: Sensor Network Deployment and Coverage Analysis

Procedure

  1. Write coverage.py: a 1000 m field, sensing radius 100 m, the sink at the centre, coverage measured on 40,000 points, connectivity by following radio links from the sink.
  2. Measure the coverage of 8 by 8, 7 by 7 and 6 by 6 grids, and compare their spacings with 1.414 × the sensing radius.
  3. Measure the coverage of 64 and of 147 randomly placed sensors over ten deployments each, and compare it with the formula 1 - e^(-λπR²).
  4. Count the sensors that can reach the sink, for the grid and the random deployments, with radio ranges of 250 m and 130 m.
  5. Draw the grid and one random deployment with their sensing disks.

Observations

DeploymentSensorsCoverageSensors reaching the sink, 250 m radioSame, 130 m radio
grid, 125.0 m apart64100.00 %6464
grid, 142.9 m apart4999.97 % (12 of 40,000 points uncovered)
grid, 166.7 m apart3695.04 %
random, ten deployments6483.8 % (79.8 to 88.3); formula 86.6 %63.8 on average, fewest 6223.0 on average, fewest 6
random, ten deployments14798.3 % (97.1 to 99.1); formula 99.0 %

Result

Grid and random deployments of sensors were simulated in a 1000 m field with a 100 m sensing radius and a 250 m radio range. A square grid covered the whole field whenever its spacing was at most 1.414 × the sensing radius, 141.4 m: at 125 m nothing was uncovered, at 142.9 m small holes appeared at the centres of the squares, and at 166.7 m 5 per cent of the field was uncovered. The same 64 sensors placed at random covered 83.8 per cent on average, a little below the formula 1 - e^(-λπR²)'s 86.6 per cent because the formula ignores the field's edges; random placement needed 147 sensors for about 99 per cent. Connectivity differed even more at a short radio range: at 130 m the grid still connected all 64 sensors to the sink and random deployments only 23 on average.

Where marks are lost

Confusing the two ranges. Coverage depends on the sensing range, connectivity on the radio range. State both.

Rounding away the holes. A grid just over the 1.414 × R spacing leaves holes too small to change a one-decimal percentage. Count uncovered points, or give more decimals.

Trusting the formula at the edges. 1 - e^(-λπR²) is for a field without edges; in a finite field it predicts more coverage than there is.

Judging random deployment by one throw. Coverage ranged from 79.8 to 88.3 per cent across ten deployments of the same number of sensors. Average several, and give the range.

munotes.in205

Practical 19: Sensor Network Deployment and Coverage Analysis

Assuming coverage implies connectivity. A field can be well covered and badly connected: at a 130 m radio range the scattered sensors covered as much as before but most could not reach the sink.

For the journal

Write: aim; sensing range and radio range; coverage and connectivity; grid and random deployment; the grid rule, spacing at most 1.414 × R, with the reason; the random-coverage formula; coverage.py and its output; the figure; the grid rule checked on both sides of its threshold; the formula against measurement, and the edge effect; connectivity at two radio ranges; observations; result.

Quick revision

  • Sensing range: how far a sensor detects events. Radio range: how far its packets reach.
  • Coverage: the share of the field within sensing range of some sensor. Connectivity: whether each sensor has a chain of links to the sink.
  • A square grid covers everything if its spacing is at most 1.414 × R: the square's centre is spacing ÷ 1.414 from its corners.
  • Random coverage is about 1 - e^(-λπR²); lower in a real field, because of its edges.
  • Here: grid 64 sensors, 100 %; random 64, 83.8 %; random needs 147 for about 99 %.
  • Connectivity at 130 m radio: grid 64 of 64; random 23.0 of 64.
  • Count uncovered points, and average several random deployments.

Questions you must be able to answer

1. What is the difference between coverage and connectivity? Coverage is whether every part of the field is within sensing range of some sensor, so that events there are detected. Connectivity is whether every sensor has a chain of radio links to the sink, so that what it detects is reported.

2. Why must a square grid's spacing be at most 1.414 × the sensing radius to cover everything? The point furthest from any sensor is the centre of each square of four sensors, at half the square's diagonal, spacing ÷ 1.414, from all four. It is covered only if that distance is at most the sensing radius.

3. The 7 by 7 grid, 142.9 m apart, covered 99.97 per cent. Where were the holes, and why so small? The centre of each square is half a diagonal from its four sensors, 101.0 m to one decimal place, just over the 100 m radius. Only the few metres around each centre are beyond every sensor's reach.

4. Why did 64 randomly placed sensors cover less than 64 in a grid? Scattered at random, some sensors fall close together and sense the same ground twice, and some parts of the field are left with none. A grid spreads them so that overlaps are small and every point is reached.

munotes.in206

Practical 19: Sensor Network Deployment and Coverage Analysis

5. What does the formula 1 - e^(-λπR²) predict for 64 sensors, and why was the measurement lower? 86.6 per cent. The formula assumes a field without edges; in a real field, sensors near the edge sense partly outside it, so less of the field is covered: 83.8 per cent on average.

6. How many randomly placed sensors does the formula say are needed for 99 per cent coverage, and how many does a grid need for 100? 147 at random, against 64 in a grid 125 m apart.

7. Why did the grid stay connected at a 130 m radio range when the random deployments did not? Every grid sensor has neighbours exactly 125 m away, within 130 m, in four directions, so a chain of links reaches every sensor. Scattered sensors have neighbours at all distances, and many had none within 130 m, which cut them, and everyone beyond them, off from the sink.

8. Why average ten random deployments? Because one deployment can be unusually good or bad: coverage ranged from 79.8 to 88.3 per cent across the ten. The average and the range describe random deployment; a single throw does not.

9. When would you still deploy sensors at random? When the field cannot be reached to place them by hand, for instance dropped from the air over a forest or a disaster zone. Then more sensors must be deployed, as the formula shows, to reach the coverage a grid would give.

10. Can a field be well covered and poorly connected? Yes. With a 130 m radio range the scattered sensors covered the field as before, but on average only 23 of the 64 could report to the sink.

Contents This chapter on its own page

munotes.in207

Chapter Twenty-Three

Practical 20: A Cellular Network and One Web Request through It

Syllabus topic Module 2, "Cellular Network Simulation with Client-Server Communication: Simulate a mobile network consisting of a cell tower, central office server, web server, and web browser, and analyze packet flow during a data request-response cycle."

Aim

To simulate a mobile network made of a phone with a web browser, a cell tower, a central office server and a web server, and to capture and analyse every packet of one request-response cycle: joining the network, finding the server, and fetching one web page.

What you need to know before you start

The parts of a cellular data network, as MU names them:

PartWhat it does
the phone, with a web browserthe user's device; it has a radio and runs the browser
the cell towerthe radio end of the network: it talks to phones over the air and passes their traffic, over a cable, to the central office
the central office serverthe operator's core: it gives each phone an address, answers its name lookups, and routes its traffic out to the internet
the web serversomewhere on the internet, holding the page the browser asks for

One request-response cycle, layer by layer. Before a browser can show a page, the phone has to do five things, and each is a different protocol:

  1. get an address, and learn its gateway and DNS server: DHCP;
  2. find the gateway's hardware address on the local link: ARP;
  3. turn the server's name into an IP address: DNS;
  4. open a connection to the server: TCP's three-way handshake;
  5. ask for the page and receive it: HTTP, a GET and a 200 OK, and close the connection.

Packet Tracer's cellular devices get their addresses from the central office server by DHCP, as the phone here does. (A real 4G phone is given its address by the operator's core network when it attaches, by a procedure of its own, not by DHCP.)

Step 1: the Packet Tracer network, as MU describes it

MU's cell tower and central office server are devices in Cisco Packet Tracer, a network simulator for students. This book does not run it: downloading Packet Tracer needs a Cisco Networking Academy account, which this book's build does not create. What follows is the procedure from Cisco's own CCNA activity 13.5.2, "Wireless Technology Exploration - Physical Mode", for a college lab where Packet Tracer is installed. It was not run for this book.

  1. Place a Central Office Server, a Cell Tower and a Smartphone (and a Server for the web page, connected to the central office's network).
  2. Connect the Cell Tower to the Central Office Server with a coaxial cable: one end to the tower's Coaxial0 interface, the other to the Central Office Server's Coaxial0/3 interface.
  3. On the Smartphone, open Config tab > 3G/4G Cell1 and check that it has received an IP address. It may take a few seconds; click DHCP Refresh if necessary.
  4. Under Settings, check that it has a default gateway and a DNS server address. The cellular network's gateway at the central office is 172.16.1.1.
  5. Open the Smartphone's web browser and ask for the web server's page, in Packet Tracer's Simulation mode to follow each packet from device to device.
munotes.in208

Practical 20: A Cellular Network and One Web Request through It

The Linux network below has the same parts, the same gateway address, and the same five-step cycle, and every packet in it is captured and shown.

Step 2: the same network on Linux

A Linux network namespace is a separate copy of the kernel's networking: its own interfaces, addresses, routes and firewall. Four namespaces make four machines on one computer, and veth pairs, two virtual interfaces joined like the ends of a cable, connect them.

Packet TracerHere
Smartphone, and its browsernamespace phone, and curl
the 3G/4G radio linkveth radio0 to radio1, slowed to 20 ms each way and 10 Mbit/s
Cell Towernamespace tower: a bridge, passing frames between the radio link and the cable
coaxial cable, Coaxial0 to Coaxial0/3veth coax0 to coax1
Central Office Servernamespace office: the gateway 172.16.1.1, DHCP and DNS (dnsmasq), and a route to the internet side
Servernamespace web: 10.0.0.10, serving a page with python3 -m http.server

network.sh builds it. Every command changes the kernel's networking, so each runs with sudo:

# network.sh: a cellular network from Linux network namespaces:
#   phone --radio-- tower --backhaul-- central office --internet-- web server
set -e
for n in phone tower office web; do
    sudo ip netns add $n
    sudo ip netns exec $n sysctl -q net.ipv6.conf.all.disable_ipv6=1 net.ipv6.conf.default.disable_ipv6=1
done
# the phone must hear the DHCP server before it has any route back to it, so the kernel's
# reverse-path check (drop a packet from a sender there is no route back to) is off in the phone
sudo ip netns exec phone sysctl -q net.ipv4.conf.all.rp_filter=0 net.ipv4.conf.default.rp_filter=0

# three links, each a virtual cable with two ends; fixed MAC addresses make every run alike
link() {    # link <end1> <ns1> <mac1> <end2> <ns2> <mac2>
    sudo ip link add $1 type veth peer name $4
    sudo ip link set $1 netns $2 address $3
    sudo ip link set $4 netns $5 address $6
}
link radio0 phone  02:00:00:00:00:01  radio1 tower  02:00:00:00:00:02
link coax0  tower  02:00:00:00:00:03  coax1  office 02:00:00:00:00:04
link wan0   office 02:00:00:00:00:05  wan1   web    02:00:00:00:00:06

# the tower passes frames between the radio side and the backhaul: a bridge, no address of its own
sudo ip netns exec tower ip link add br0 type bridge
sudo ip netns exec tower ip link set radio1 master br0
sudo ip netns exec tower ip link set coax0 master br0
for i in lo br0 radio1 coax0; do sudo ip netns exec tower ip link set $i up; done
# the radio hop is the slow one: 20 ms each way, 10 Mbit/s
sudo ip netns exec tower tc qdisc add dev radio1 root netem delay 20ms rate 10mbit
sudo ip netns exec phone tc qdisc add dev radio0 root netem delay 20ms rate 10mbit

# the central office: gateway of the cellular network and a router to the internet side
sudo ip netns exec office ip addr add 172.16.1.1/24 dev coax1
sudo ip netns exec office ip addr add 10.0.0.1/24 dev wan0
for i in lo coax1 wan0; do sudo ip netns exec office ip link set $i up; done
sudo ip netns exec office sysctl -q net.ipv4.ip_forward=1

# the web server, on the internet side
sudo ip netns exec web ip addr add 10.0.0.10/24 dev wan1
for i in lo wan1; do sudo ip netns exec web ip link set $i up; done
sudo ip netns exec web ip route add default via 10.0.0.1

# the phone's radio is up, with no address yet: it will ask for one
for i in lo radio0; do sudo ip netns exec phone ip link set $i up; done
echo "network ready"
munotes.in209

Practical 20: A Cellular Network and One Web Request through It

The tower has no IP address. A bridge passes frames from one side to the other without looking at their IP addresses, as a cell tower passes traffic between the air and the operator's network. The radio link is the slow one: tc qdisc ... netem holds every frame 20 ms and limits the link to 10 Mbit/s, in each direction. IPv6 is switched off in every namespace so that its automatic chatter does not appear in the capture.

The phone's reverse-path filter is off. Linux drops an arriving packet when it has no route back to the packet's sender, a guard against forged sender addresses called the reverse-path filter, rp_filter. Until DHCP has given it an address, the phone has no routes at all, so the filter would drop the central office's answers. In this lab it did: with the filter on (value 2), the phone never received the server's Offer, although the tower saw it pass. It is switched off in the phone's namespace, and nowhere else.

$ bash network.sh
network ready

Step 3: the central office and the web server

office.sh starts the central office's services with dnsmasq: a DHCP server handing out addresses from 172.16.1.100 to 172.16.1.150, with 172.16.1.1 as both gateway and DNS server, and a DNS server that knows one name, www.cell.test, at the web server's address. It also starts the web server.

munotes.in210

Practical 20: A Cellular Network and One Web Request through It

# office.sh: the central office's services: DHCP for phones, and DNS that knows the web server
sudo ip netns exec office dnsmasq --interface=coax1 --bind-interfaces --except-interface=lo \
    --dhcp-range=172.16.1.100,172.16.1.150,1h --dhcp-option=option:router,172.16.1.1 \
    --dhcp-option=option:dns-server,172.16.1.1 --address=/www.cell.test/10.0.0.10 \
    --no-ping --no-resolv --no-hosts --pid-file=/tmp/dnsmasq.pid --log-facility=/tmp/dnsmasq.log
mkdir -p /tmp/site && printf '<html><body>munotes cell test page</body></html>\n' > /tmp/site/index.html
# setsid -f: the web server runs on its own, cut off from this terminal, after the script ends
sudo ip netns exec web setsid -f python3 -m http.server 80 --directory /tmp/site \
    < /dev/null > /tmp/web.log 2>&1
sleep 1
echo "central office and web server running"

--no-ping stops dnsmasq from first pinging an address to check that it is free, which would add packets to the capture that depend on timing. dnsmasq puts itself in the background. The web server does not, so setsid -f starts it in a session of its own, cut off from the terminal, and it goes on serving after office.sh has ended. Tested on Ubuntu 22.04's default settings, a web server started with a plain & at the end of the line instead did not outlive the script for long: in four runs of five the phone received an empty page, and in the fifth the server was gone a moment after serving it.

$ bash office.sh
central office and web server running

Step 4: one request and its response

The phone needs a DHCP client, the program that asks for an address. Ubuntu's own, dhclient, is not used here. It hands what it receives to scripts written for the computer's real network connection, and on Ubuntu 22.04 one of them passes the DNS server on to the computer's own name resolver, systemd-resolved, which is outside the phone. phone-dhcp.py is a DHCP client of its own, in standard Python: it makes the four messages byte by byte, and sets up only the phone.

A DHCP message is a fixed part of 236 bytes, a 4-byte magic cookie, and then options, each a code, a length and a value (RFC 2131 and RFC 2132). The fields that matter here:

BytesFieldIn the phone's messages
0op: 1 for a request, 2 for a reply1
1 and 2hardware type and address length1 (Ethernet) and 6
4 to 7xid, the transaction ida random number, the same in all of one exchange
10 and 11flags0x8000, the broadcast flag: send your replies to everyone
16 to 19yiaddr, "your address"empty; the server's replies carry the address here
28 to 43chaddr, the client's hardware address02:00:00:00:00:01
236 to 239the magic cookie, 99.130.83.99marks the start of the options
from 240options53, the message type; 55, asking for 1 (subnet mask), 3 (gateway) and 6 (DNS server); in the Request also 50, the address wanted, and 54, the server chosen; 255 ends them
munotes.in211

Practical 20: A Cellular Network and One Web Request through It

# phone-dhcp.py: the phone's DHCP client. It sends Discover and Request, receives Offer and ACK,
# then gives the phone the address, gateway and DNS server it was offered. Runs inside the phone.
import os, random, socket, struct, subprocess

IFACE, MAC = 'radio0', bytes.fromhex('020000000001')
XID = random.getrandbits(32)        # transaction id: the phone knows its own replies by it

def message(kind, options=b''):
    # op 1 (a request), hardware type 1 (Ethernet), MAC length 6, hops 0, the transaction id,
    # seconds 0, flags 0x8000 (broadcast your reply: the phone has no address to receive it on),
    # four addresses left empty, the MAC (padded to 16 bytes), 192 empty bytes, the magic cookie
    fixed = struct.pack('!BBBBIHH16s16s192s4s', 1, 1, 6, 0, XID, 0, 0x8000,
                        bytes(16), MAC, bytes(192), bytes([99, 130, 83, 99]))
    # option 53 is the message type; option 55 asks for 1 (mask), 3 (gateway), 6 (DNS); 255 ends
    return fixed + bytes([53, 1, kind, 55, 3, 1, 3, 6]) + options + bytes([255])

def reply(sock, kind):
    while True:
        data = sock.recv(1500)
        if data[4:8] != struct.pack('!I', XID):
            continue                          # someone else's reply
        options, i = {}, 240                  # options start after 236 fixed bytes and the cookie
        while i < len(data) and data[i] != 255:
            if data[i] == 0:                  # a padding byte
                i += 1
                continue
            options[data[i]] = data[i + 2:i + 2 + data[i + 1]]
            i += 2 + data[i + 1]
        if options.get(53) == bytes([kind]):
            return socket.inet_ntoa(data[16:20]), options      # bytes 16-19: "your address"

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, IFACE.encode())
s.bind(('0.0.0.0', 68))                       # 68 is the client's port, 67 the server's
s.settimeout(10)

s.sendto(message(1), ('255.255.255.255', 67))
print('sent      Discover')
offered, options = reply(s, 2)
server = options[54]                          # option 54: which server made the offer
print('received  Offer of %s from %s' % (offered, socket.inet_ntoa(server)))
s.sendto(message(3, bytes([50, 4]) + socket.inet_aton(offered) + bytes([54, 4]) + server),
         ('255.255.255.255', 67))
print('sent      Request for %s' % offered)
address, options = reply(s, 5)
print('received  ACK')

prefix = bin(struct.unpack('!I', options[1])[0]).count('1')     # 255.255.255.0 is /24
gateway = socket.inet_ntoa(options[3][:4])
dns = socket.inet_ntoa(options[6][:4])
subprocess.run(['ip', 'addr', 'add', '%s/%d' % (address, prefix), 'dev', IFACE], check=True)
subprocess.run(['ip', 'route', 'add', 'default', 'via', gateway], check=True)
os.makedirs('/etc/netns/phone', exist_ok=True)
with open('/etc/netns/phone/resolv.conf', 'w') as f:  # the phone's own DNS setting (see ip-netns)
    f.write('nameserver %s\n' % dns)
print('phone     address %s/%d, gateway %s, DNS server %s' % (address, prefix, gateway, dns))

Three details in it. The broadcast flag: RFC 2131 has a client that cannot receive a packet addressed to it before it has taken on its address set this flag, and this client, listening through an ordinary socket, is such a client, so the server broadcasts its replies. The transaction id: every client on the link hears every broadcast, and the id, chosen at random and copied into the server's replies, is how the phone picks out its own. The DNS server goes into /etc/netns/phone/resolv.conf: ip netns exec phone shows a program that file as its /etc/resolv.conf, the file that names the DNS server, so the browser in the phone uses the one DHCP gave.

munotes.in212

Practical 20: A Cellular Network and One Web Request through It

request.sh starts a capture at the tower, runs the phone's DHCP client, and fetches the page with curl, as a browser would:

# request.sh: the phone joins the network and fetches one web page; the tower records every frame
sudo ip netns exec tower setsid -f tcpdump -i br0 -nn -w capture.pcap < /dev/null > /dev/null 2>&1
sleep 1
# 1. an address, by DHCP from the central office
sudo ip netns exec phone python3 phone-dhcp.py
# 2. the page: a DNS lookup and an HTTP request, made by curl as a browser would
echo "page      $(sudo ip netns exec phone curl -4 -s http://www.cell.test/)"
sleep 1
sudo ip netns pids tower | xargs sudo kill      # stop the capture: tcpdump is all that runs there
sleep 1

ip netns pids tower lists the programs running inside the tower's namespace. The only one is tcpdump, so killing that list stops the capture, and tcpdump finishes writing the file as it stops.

$ bash request.sh
sent      Discover
received  Offer of 172.16.1.145 from 172.16.1.1
sent      Request for 172.16.1.145
received  ACK
phone     address 172.16.1.145/24, gateway 172.16.1.1, DNS server 172.16.1.1
page      <html><body>munotes cell test page</body></html>

The central office offered 172.16.1.145, the phone asked for it, and the ACK confirmed it with a /24 subnet mask, the gateway 172.16.1.1 and the DNS server 172.16.1.1, exactly the gateway of Cisco's activity. The browser then received the page from the web server on the far side of the central office.

Step 5: the packet flow

The tower saw every frame of that cycle. tcpdump -r reads the capture back; the sed only removes each TCP line's options, to keep the lines short:

$ tcpdump -nn -t -r capture.pcap 2>/dev/null | sed 's/, options \[[^]]*\]//'
IP 0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 02:00:00:00:00:01, length 249
IP 172.16.1.1.67 > 255.255.255.255.68: BOOTP/DHCP, Reply, length 300
IP 0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 02:00:00:00:00:01, length 261
IP 172.16.1.1.67 > 255.255.255.255.68: BOOTP/DHCP, Reply, length 300
ARP, Request who-has 172.16.1.1 tell 172.16.1.145, length 28
ARP, Reply 172.16.1.1 is-at 02:00:00:00:00:04, length 28
IP 172.16.1.145.45445 > 172.16.1.1.53: 49531+ A? www.cell.test. (31)
IP 172.16.1.145.45445 > 172.16.1.1.53: 41593+ AAAA? www.cell.test. (31)
IP 172.16.1.1.53 > 172.16.1.145.45445: 49531* 1/0/0 A 10.0.0.10 (47)
IP 172.16.1.1.53 > 172.16.1.145.45445: 41593 Refused 0/0/0 (31)
IP 172.16.1.145.39554 > 10.0.0.10.80: Flags [S], seq 2201636002, win 64240, length 0
IP 10.0.0.10.80 > 172.16.1.145.39554: Flags [S.], seq 431014673, ack 2201636003, win 65160, length 0
IP 172.16.1.145.39554 > 10.0.0.10.80: Flags [.], ack 1, win 502, length 0
IP 172.16.1.145.39554 > 10.0.0.10.80: Flags [P.], seq 1:78, ack 1, win 502, length 77: HTTP: GET / HTTP/1.1
IP 10.0.0.10.80 > 172.16.1.145.39554: Flags [.], ack 78, win 509, length 0
IP 10.0.0.10.80 > 172.16.1.145.39554: Flags [P.], seq 1:187, ack 78, win 509, length 186: HTTP: HTTP/1.0 200 OK
IP 10.0.0.10.80 > 172.16.1.145.39554: Flags [FP.], seq 187:236, ack 78, win 509, length 49: HTTP
IP 172.16.1.145.39554 > 10.0.0.10.80: Flags [.], ack 187, win 501, length 0
IP 172.16.1.145.39554 > 10.0.0.10.80: Flags [F.], seq 78, ack 237, win 501, length 0
IP 10.0.0.10.80 > 172.16.1.145.39554: Flags [.], ack 79, win 509, length 0
munotes.in213

Practical 20: A Cellular Network and One Web Request through It

(Port numbers, sequence numbers and DNS ids are chosen at random each run, so yours will differ; the order and the kinds of packet will not.)

Twenty frames, in five groups, exactly the five steps of the cycle.

1. An address, by DHCP: four frames. The phone, with no address yet, broadcasts from 0.0.0.0 to 255.255.255.255, port 67, the DHCP server's. The central office answers from 172.16.1.1 to 255.255.255.255, port 68, the client's: a broadcast too, because the phone set the broadcast flag. tcpdump calls each of the phone's messages a BOOTP Request and each of the server's a Reply, from the op field; the DHCP message type is an option inside, which tcpdump -v prints:

$ tcpdump -nn -v -r capture.pcap port 67 or port 68 2>/dev/null | grep "DHCP-Message"
	    DHCP-Message (53), length 1: Discover
	    DHCP-Message (53), length 1: Offer
	    DHCP-Message (53), length 1: Request
	    DHCP-Message (53), length 1: ACK

The phone discovers a server, the server offers an address, the phone requests it, and the server acknowledges, with the lease and the gateway and DNS server in it. The lengths are the phone's own arithmetic: the Discover is the fixed part, the cookie and 9 bytes of options, 236 + 4 + 9 = 249 bytes, and the Request adds options 50 and 54, 6 bytes each, 249 + 6 + 6 = 261. The server's replies are 300 bytes long.

2. The central office's hardware address, by ARP: two frames. The phone's first packet goes to 172.16.1.1, its DNS server and its gateway, so it asks, by broadcast, who has 172.16.1.1, and the central office replies with its MAC address, 02:00:00:00:00:04, the one network.sh gave coax1. The same address serves every later packet, including those for the web server, which go by way of the gateway.

3. The server's address, by DNS: four frames. curl asks the DNS server at 172.16.1.1, port 53, for www.cell.test: once for an IPv4 address (A?) and once for an IPv6 one (AAAA?). The central office answers the first with 10.0.0.10 (1/0/0: one answer) and refuses the second: it knows no IPv6 address for the name and has no other server to ask.

munotes.in214

Practical 20: A Cellular Network and One Web Request through It

4. A connection, by TCP: three frames. The phone sends a SYN ([S]) to 10.0.0.10, port 80; the server answers SYN-ACK ([S.]); the phone acknowledges ([.], ack 1). From here tcpdump numbers the bytes of the connection from 1.

5. The page, by HTTP, and the close: seven frames. The phone sends its request, 77 bytes, GET / HTTP/1.1, with the push flag ([P.]); the server acknowledges it, then sends HTTP/1.0 200 OK and its headers, 186 bytes, then the page itself, 49 bytes, with FIN to close its side ([FP.]). The phone acknowledges up to byte 187, closes its own side ([F.]), and the server acknowledges that. ack 237 is the last byte of the page plus one: 186 + 49 = 235 bytes of response, numbered from 1, and one more for the FIN.

Every packet the web server saw passed through the central office. The phone's packets to 10.0.0.10 go to its gateway, 172.16.1.1, which routes them out on wan0; the tower in between only passed frames. That is the path Cisco's activity describes: phone, cell tower, coaxial cable, central office, internet.

The radio link's delay shows in the times. With -ttttt, tcpdump gives each frame's time since the first:

$ tcpdump -nn -ttttt -r capture.pcap 2>/dev/null | cut -c1-60
 00:00:00.000000 IP 0.0.0.0.68 > 255.255.255.255.67: BOOTP/D
 00:00:00.000486 IP 172.16.1.1.67 > 255.255.255.255.68: BOOT
 00:00:00.044759 IP 0.0.0.0.68 > 255.255.255.255.67: BOOTP/D
 00:00:00.046849 IP 172.16.1.1.67 > 255.255.255.255.68: BOOT
 00:00:00.125913 ARP, Request who-has 172.16.1.1 tell 172.16
 00:00:00.125949 ARP, Reply 172.16.1.1 is-at 02:00:00:00:00:
 00:00:00.167937 IP 172.16.1.145.45445 > 172.16.1.1.53: 4953
 00:00:00.167938 IP 172.16.1.145.45445 > 172.16.1.1.53: 4159
 00:00:00.168203 IP 172.16.1.1.53 > 172.16.1.145.45445: 4953
 00:00:00.168247 IP 172.16.1.1.53 > 172.16.1.145.45445: 4159
 00:00:00.211303 IP 172.16.1.145.39554 > 10.0.0.10.80: Flags
 00:00:00.211382 IP 10.0.0.10.80 > 172.16.1.145.39554: Flags
 00:00:00.254325 IP 172.16.1.145.39554 > 10.0.0.10.80: Flags
 00:00:00.254329 IP 172.16.1.145.39554 > 10.0.0.10.80: Flags
 00:00:00.254431 IP 10.0.0.10.80 > 172.16.1.145.39554: Flags
 00:00:00.263551 IP 10.0.0.10.80 > 172.16.1.145.39554: Flags
 00:00:00.263688 IP 10.0.0.10.80 > 172.16.1.145.39554: Flags
 00:00:00.305932 IP 172.16.1.145.39554 > 10.0.0.10.80: Flags
 00:00:00.305935 IP 172.16.1.145.39554 > 10.0.0.10.80: Flags
 00:00:00.306012 IP 10.0.0.10.80 > 172.16.1.145.39554: Flags

The tower is between the two halves of the network, and its times show which half is slow. On the cable side, an answer passes the tower almost as soon as its question: the central office's Offer 0.5 ms after the Discover and its ACK 2 ms after the Request, its DNS answers 0.3 ms after the questions, the web server's SYN-ACK 0.08 ms after the SYN. On the radio side, the phone's next frame comes more than 40 ms after the answer it follows, for instance the SYN-ACK at 0.211 s and the phone's ACK at 0.254 s: the answer spends 20 ms crossing the radio link to the phone, and the phone's reply 20 ms crossing back. Every exchange with the phone pays that round trip over the air, and one request-response cycle needs several: DHCP twice, then ARP, DNS, TCP's handshake and the request.

munotes.in215

Practical 20: A Cellular Network and One Web Request through It

Two gaps are not the network. The server's response passed the tower 9 ms after the GET, while its acknowledgement of the same GET took 0.1 ms over the same path, so those 9 ms were spent inside the web server. And the 79 ms between the DHCP ACK and the ARP request are the radio round trip, 40 ms, and about 39 ms of the phone's own work: taking on its address, route and DNS server, and starting the browser, whose first packet needed the central office's hardware address. (Times differ a little from run to run.)

Step 6: taking the network down

The central office's services, the web server and the four namespaces stay until they are removed or the computer restarts. clean.sh stops every program running inside the namespaces, found with ip netns pids, and deletes the namespaces, which removes their interfaces with them:

# clean.sh: stop the programs running inside the four namespaces, then remove the network
for n in phone tower office web; do
    sudo ip netns pids $n | xargs -r sudo kill
    sudo ip netns del $n
done
sudo rm -r /etc/netns/phone
echo "network removed"

xargs -r runs kill only when the list is not empty: the phone and the tower have nothing left running by now.

$ bash clean.sh
network removed

Procedure

  1. Describe the Packet Tracer network MU names, from Cisco's activity, and why it was not run here.
  2. Build the same network with Linux namespaces: phone, tower, central office and web server, a slowed radio link and a cable, with network.sh.
  3. Start the central office's DHCP and DNS and the web server with office.sh.
  4. With request.sh, capture at the tower while the phone's DHCP client, phone-dhcp.py, gets an address and curl fetches the page.
  5. Read the capture: the frames of DHCP, ARP, DNS, TCP and HTTP, the DHCP message types, and the times.
  6. Remove the network with clean.sh.

Observations

Step of the cycleProtocolFramesWhat happened
addressDHCP4Discover, Offer, Request, ACK, all four broadcast; the phone got 172.16.1.145/24, gateway and DNS 172.16.1.1
gateway's MACARP2172.16.1.1 is at 02:00:00:00:00:04
server's addressDNS4www.cell.test is 10.0.0.10; the IPv6 query refused
connectionTCP3SYN, SYN-ACK, ACK
page and closeHTTP and TCP7GET (77 bytes), 200 OK (186 bytes), the page (49 bytes), FINs and ACKs
all20about 0.31 s from the first frame to the last
munotes.in216

Practical 20: A Cellular Network and One Web Request through It

Result

A mobile network of a phone with a browser, a cell tower, a central office server and a web server was built from Linux network namespaces, in the arrangement of Cisco Packet Tracer's cellular activity, with the tower bridging a slowed radio link to a cable to the central office. One request-response cycle took 20 frames, captured at the tower: four of DHCP, all broadcast, in which the central office gave the phone 172.16.1.145/24 with 172.16.1.1 as gateway and DNS server; two of ARP for the central office's hardware address; four of DNS, which resolved www.cell.test to 10.0.0.10; three of TCP's handshake; and seven for the HTTP request, the 200 OK response and page, and the connection's close. Every exchange with the phone took over 40 ms at the tower, the round trip over the 20 ms radio link, while on the cable side the central office answered within 2 ms and the web server acknowledged within a millisecond.

Where marks are lost

Claiming to have run Packet Tracer without it. Its download needs a Networking Academy account. Give its procedure from Cisco's activity, say that you did not run it if you did not, and show the same cycle where you can capture it.

Leaving out the steps before the page. A browser's request is not one packet: without DHCP, ARP and DNS first, there is no address, no route and no server address. Show all five steps.

Giving the tower an IP address and routing through it. A cell tower passes the phones' traffic on to the operator's network; it is not the gateway. Here it is a bridge; the gateway is the central office.

Reading tcpdump's relative sequence numbers as absolute. After the handshake tcpdump numbers the connection's bytes from 1. ack 237 means 236 bytes, and the FIN, received.

Expecting the same ports and ids. Source ports, sequence numbers and DNS ids are random each run. The order and kinds of frames are what must match.

Letting background noise into the capture. IPv6's automatic messages and dnsmasq's address check add frames whose number depends on timing. Turn them off, or explain every extra frame.

Starting a server in the background with a plain & after sudo. Tested on Ubuntu 22.04's default settings, a web server started that way from a script did not keep running, and in four runs of five the browser got an empty page. Start it with setsid -f, as office.sh does.

munotes.in217

Practical 20: A Cellular Network and One Web Request through It

For the journal

Write: aim; the four parts of a cellular data network and what each does; the five steps of a request-response cycle and the protocol of each; the Packet Tracer procedure from Cisco's activity, marked as not run if you did not run it; the table of Packet Tracer devices against namespaces; network.sh, office.sh, phone-dhcp.py, request.sh and clean.sh; the layout of a DHCP message; the capture, divided into its five steps and explained frame by frame; the DHCP message types; the times, and what the radio link's delay does to them; observations; result.

Quick revision

  • Phone and browser, cell tower (radio side), central office server (addresses, names, routing), web server.
  • A request-response cycle: DHCP (address), ARP (gateway's MAC), DNS (server's IP), TCP (connection), HTTP (request and response), then TCP's close.
  • DHCP: Discover, Offer, Request, ACK; gives address, gateway and DNS server.
  • A DHCP message: 236 fixed bytes, the magic cookie, then options (53 the type, 55 what is asked for, 50 the address wanted, 54 the server chosen); the transaction id pairs replies with requests; the broadcast flag asks for replies to everyone.
  • Packet Tracer's cell tower connects to the Central Office Server by coaxial cable, Coaxial0 to Coaxial0/3; the phone's Config > 3G/4G Cell1; gateway 172.16.1.1.
  • Linux: network namespaces are separate network stacks; veth pairs are virtual cables; a bridge passes frames without routing.
  • tc ... netem delay 20ms makes a slow radio link: every exchange with the phone costs a 40 ms round trip.
  • tcpdump -w captures, tcpdump -r reads, -v shows DHCP message types, -ttttt times from the first frame.
  • Here: 20 frames, 4 DHCP, 2 ARP, 4 DNS, 3 TCP handshake, 7 HTTP and close, in about 0.31 s.

Questions you must be able to answer

1. What does each of the four parts of the network do? The phone runs the browser and talks over the radio. The cell tower passes the phones' traffic between the air and the operator's network. The central office server gives phones their addresses, answers their name lookups and routes their traffic to the internet. The web server holds the page.

2. Why was Packet Tracer not run, and what was done instead? Downloading it needs a Cisco Networking Academy account, which this book's build does not create. Its procedure is given from Cisco's own activity, marked as not run, and the same network was built with Linux network namespaces, where every packet could be captured.

3. In what order do DHCP, ARP, DNS and TCP happen in the cycle, and why in that order? DHCP first, because the phone needs an address, a gateway and a DNS server before it can send anything else; ARP next, to find the hardware address of 172.16.1.1, its DNS server and gateway; DNS, to turn the server's name into an address; then TCP, to connect to that address, through the gateway, before HTTP can ask for the page.

munotes.in218

Practical 20: A Cellular Network and One Web Request through It

4. Name the four DHCP messages and what each does. Discover: the client broadcasts to find a server. Offer: a server offers an address. Request: the client asks for the offered address. ACK: the server confirms it, with the lease, gateway and DNS server.

5. Why is the DHCP Discover sent from 0.0.0.0 to 255.255.255.255? Because the phone has no address yet and does not know where the server is, so it sends from no address to everyone on the link.

6. Why did the central office broadcast its Offer and ACK instead of sending them to 172.16.1.145? Because the phone set the broadcast flag in its messages. It has not yet taken on the address, and a client that cannot receive a packet addressed to it until then asks for its replies to be broadcast (RFC 2131).

7. What is the transaction id for? Every client on the link hears every broadcast reply. The phone chooses the id at random, the server copies it into its replies, and the phone keeps only the replies that carry its own.

8. Why was the reverse-path filter switched off in the phone? The filter drops a packet from a sender the kernel has no route back to. Before DHCP the phone has no routes at all, so with the filter on it dropped the central office's Offer, which the tower had seen pass.

9. What did the DNS server answer, and why was one query refused? It answered the IPv4 query for www.cell.test with 10.0.0.10. It refused the IPv6 query because it knew no IPv6 address for the name and had no other server to ask.

10. Explain the three frames of TCP's handshake in the capture. The phone sends SYN with its starting sequence number; the server answers SYN-ACK, with its own starting number and acknowledging the phone's plus one; the phone acknowledges the server's plus one. Both sides then know each other's starting numbers, and the connection is open.

11. The phone's last frame is ack 237. Why 237? The server sent 186 bytes of status line and headers and 49 bytes of page, 235 bytes, numbered from 1, and then a FIN, which also counts as one: the next byte the phone expects is 237.

munotes.in219

Practical 20: A Cellular Network and One Web Request through It

12. Why is the tower a bridge and not a router? A cell tower relays the phones' traffic to the operator's network without being the gateway itself. As a bridge it passes frames between the radio link and the cable without looking at IP addresses; the central office is the gateway that routes.

13. At the tower, answers from the central office came within 2 ms of the question, but the phone's next frame came over 40 ms after an answer. Why? The tower sits between the fast cable side and the slow radio side. The central office is on the cable side. The phone is across the radio link, which delays every frame 20 ms each way, so an answer reaches the phone 20 ms after passing the tower, and the phone's reply takes 20 ms more to come back.

14. The DHCP ACK passed the tower 79 ms before the phone's ARP request. Where did that time go? 40 ms of it is the radio round trip: the ACK took 20 ms to reach the phone, and the ARP request 20 ms to come back. The other 39 ms or so were the phone's own work: taking on the address, route and DNS server it was given, and starting the browser, whose first packet needed the central office's hardware address.

15. Why were the MAC addresses fixed and IPv6 switched off? To make every run capture the same frames in the same order: random MAC addresses would change every run, and IPv6's automatic messages would add frames whose number depends on timing.

Contents This chapter on its own page

munotes.in220

Chapter Twenty-Four

The Two-Hour Paper: Sitting the Examination

Syllabus topic Module 1 and Module 2, and MU's external examination for this paper: "A Semester End Practical Examination of 2 hours duration for 30 marks", "Q. 1 Module 1 15", "Q. 2 Module 2 15", "Certified Journal is compulsory for appearing at the time of Practical Exam", "Minimum 80% practical are required to be completed."

Aim

To sit the Semester End Practical Examination: two hours, two practical questions of fifteen marks each, one from each module, each done in its own laboratory.

What the paper looks like

The first chapter of this book has MU's figures; here they are in the form they matter on the day.

Duration2 hours
Total30 marks
Q.1a practical question based on Module 1, 15 marks
Q.2a practical question based on Module 2, 15 marks
Required to sita certified journal, and at least 16 of the 20 practicals completed

Neither question is known in advance. Q.1 is one of the ten Module 1 exercises and is done in TinyOS and TOSSIM. Q.2 is one of the ten Module 2 exercises: four are done wholly in NS-2, three pair NS-2 with a short program of your own, two are programs alone, and the last builds a small real network (see "If the question is another exercise" below). Any of the twenty can be set, which is why sixteen is a floor and not a target.

What a full-marks answer contains

Every answer has the parts every write-up in your journal has. Write the headings on the answer sheet first, before touching the keyboard: they take a minute, and they stop you forgetting the parts that are not code.

  1. Aim. One or two lines, in the question's own words.
  2. Theory. Four or five sentences: the mechanism the question is about, and what decides the result.
  3. Procedure. Numbered steps, in words.
  4. Program. Every file. For Module 1 that is the configuration, the module, any header, the Makefile and the Python script that drives TOSSIM; for Module 2, the Tcl script and the awk script that reads its trace.
  5. Output. Exactly what the machine printed.
  6. Observations. The table of what was measured.
  7. Result. Two or three lines saying what the run showed, with its numbers.

Parts 5 and 6 are where marks are lost, and they are the cheapest to get right. A simulation with no output is an unfinished practical, and when the question says "analyse" or "measure", an answer with no table has not answered it.

The two hours

The same plan as the first chapter's, laid out for both questions:

MinutesThe question you are surer ofThe other question
0 to 5read both questions and choose the order
5 to 12aim, theory and procedure, on paper
12 to 45type, build, run, fix, get output
45 to 55write the output and the observation table
55 to 62aim, theory and procedure
62 to 100type, run, fix, get output
100 to 110output and observation table
110 to 120check both: output written, table filled, result stated
munotes.in221

The Two-Hour Paper: Sitting the Examination

Start with the one you are surer of, and stop at the hour. The other fifteen marks are in the other laboratory, and a complete answer to one question with a half answer to the other beats two answers that each stop before their output.

What to type first, and how much is enough

Type the short files first. A Module 1 answer is five files, and three of them are short: the Makefile is five lines, the configuration fifteen, the header thirteen. With those in place, type the module and build at once: nesC names the file and the line of every error, and it is far better to meet them at minute 25 than at minute 44. Type the Python driver while nothing else is left to fix.

A Module 2 answer is a Tcl script and an awk script. The first thirty lines of the Tcl script, the val settings and node-config, are the same in every wireless simulation (chapter 13); know them well enough to type without looking. Then the nodes, their movement, the traffic, and finish. The awk script comes last, and it is a few lines.

Enough is what the question asks for, on a run that repeats. Q.1 below asks for signal strength and packet loss at varying gains: a sender, a receiver, a gain set per run, and a count. It does not ask for LEDs, a serial link, or a noisy room. Q.2 asks for three measures for different protocols: one script that takes the protocol as an argument, and an awk script that prints the three measures. It does not ask for an animation, so the script writes no NAM file. And every run is seeded, so that the examiner who runs it again gets your numbers.

Q.1, worked: a Module 1 question

Q.1 Simulate packet transmission between two motes and analyse signal strength and packet loss under varying radio gain values. (15 marks)

Aim. To simulate packet transmission between two motes in TOSSIM, and to analyse the received signal strength and the packet loss at several radio gains.

Theory. TOSSIM models the radio link between two motes by its gain, in dB, set separately for each direction, and the background by a noise model built from a recorded noise trace. The sender transmits at 0 dBm, so a packet arrives with the strength of the gain. TOSSIM works out each packet's signal-to-noise ratio and decides whether it arrives from a curve fitted to measurements of the CC2420 radio: below about 4 dB of SNR almost no packet survives, above about 6 dB almost every one does (Practical 7). The receiver's TossimPacket.strength() reports what arrived, signal plus noise, in dBm, TOSSIM's version of the CC2420's RSSI.

munotes.in222

The Two-Hour Paper: Sitting the Examination

Procedure.

  1. Write a TinyOS application in which mote 1 sends 50 numbered packets to mote 2, one every 100 binary milliseconds, and mote 2 logs each packet it receives with its strength.
  2. Build it for TOSSIM.
  3. Write a Python script that sets one gain in both directions, gives both motes the quiet noise trace, runs the simulation with a fixed seed and counts sent and received packets.
  4. Run it at gains of minus 80, 91, 92, 93 and 94 dB.
  5. Tabulate the packets received, the loss and the mean strength at each gain.

Program.

$ mkdir ~/Q1
$ cd ~/Q1

Pair.h, the constants and the packet's layout, shared by the module and the configuration:

#ifndef PAIR_H
#define PAIR_H
enum {
  AM_PAIR = 7,
  SENDER = 1,
  RECEIVER = 2,
  PACKETS = 50,        /* how many packets the sender sends */
  INTERVAL = 100       /* one every 100 binary milliseconds */
};
typedef nx_struct pair_msg {
  nx_uint16_t seq;     /* numbered, so a loss can be pinned to a packet */
} pair_msg_t;
#endif

PairC.nc, the module. The sender's timer sends; the receiver's Receive event logs:

#include "Pair.h"

module PairC {
  uses interface Boot;
  uses interface Timer<TMilli>;
  uses interface SplitControl as RadioControl;
  uses interface AMSend;
  uses interface Receive;
  uses interface TossimPacket;
}
implementation {
  message_t packet;
  bool busy = FALSE;
  uint16_t sent = 0;

  event void Boot.booted() {
    call RadioControl.start();
  }

  event void RadioControl.startDone(error_t err) {
    if (TOS_NODE_ID == SENDER)
      call Timer.startPeriodic(INTERVAL);
  }

  event void RadioControl.stopDone(error_t err) {}

  event void Timer.fired() {
    pair_msg_t* m;
    if (sent == PACKETS) {
      call Timer.stop();
      return;
    }
    if (busy)
      return;
    m = (pair_msg_t*) call AMSend.getPayload(&packet, sizeof(pair_msg_t));
    m->seq = sent;
    if (call AMSend.send(RECEIVER, &packet, sizeof(pair_msg_t)) == SUCCESS) {
      busy = TRUE;
      sent++;
      dbg("Pair", "sent %u\n", m->seq);
    }
  }

  event void AMSend.sendDone(message_t* msg, error_t err) {
    busy = FALSE;
  }

  event message_t* Receive.receive(message_t* msg, void* payload, uint8_t len) {
    pair_msg_t* m = (pair_msg_t*) payload;
    dbg("Pair", "received %u strength %d\n", m->seq, call TossimPacket.strength(msg));
    return msg;
  }
}

PairAppC.nc, the configuration. TossimPacket comes from TossimActiveMessageC, wired in directly as in Practical 7:

#include "Pair.h"

configuration PairAppC {}
implementation {
  components MainC, PairC as App, new TimerMilliC();
  components ActiveMessageC, TossimActiveMessageC;
  components new AMSenderC(AM_PAIR), new AMReceiverC(AM_PAIR);

  App.Boot -> MainC;
  App.Timer -> TimerMilliC;
  App.RadioControl -> ActiveMessageC;
  App.AMSend -> AMSenderC;
  App.Receive -> AMReceiverC;
  App.TossimPacket -> TossimActiveMessageC;
}

The Makefile, the same five lines as every application in this book but for its first:

COMPONENT=PairAppC
override GCC = gcc-10
export GCC
PFLAGS += -fgnu89-inline -fsigned-char
include $(MAKERULES)
$ make micaz sim 2>&1 | tail -1
*** Successfully built micaz TOSSIM library.

run.py takes the gain as its argument, so one script serves every row of the table:

munotes.in223

The Two-Hour Paper: Sitting the Examination

# run.py: one run at the link gain given, in dB: python2 run.py <gain>
from TOSSIM import *
import os, sys

gain = float(sys.argv[1])
t = Tossim([])
t.randomSeed(1)
r = t.radio()
r.add(1, 2, gain)                  # sender to receiver
r.add(2, 1, gain)                  # and back
noise = open(os.environ["TOSDIR"] + "/lib/tossim/noise/casino-lab.txt").readlines()[:100]
for n in (1, 2):
    m = t.getNode(n)
    for line in noise:
        m.addNoiseTraceReading(int(line))
    m.createNoiseModel()
    m.bootAtTime(n * 1000)

log = open("pair.log", "w")
t.addChannel("Pair", log)
while t.time() < 7 * t.ticksPerSecond():     # 50 packets take about 5 s
    t.runNextEvent()
log.close()

sent = received = 0
strengths = []
for line in open("pair.log"):
    words = line.split()
    if words[2] == "sent":
        sent += 1
    elif words[2] == "received":
        received += 1
        strengths.append(int(words[-1]))
loss = 100.0 * (sent - received) / sent
if strengths:
    mean = "%.1f dBm" % (float(sum(strengths)) / len(strengths))
else:
    mean = "none"
print "gain %4d dB: sent %d, received %2d, loss %5.1f %%, mean strength %s" % (gain, sent, received, loss, mean)

Output.

$ for g in -80 -91 -92 -93 -94; do python2 run.py $g; done
gain  -80 dB: sent 50, received 50, loss   0.0 %, mean strength -80.0 dBm
gain  -91 dB: sent 50, received 50, loss   0.0 %, mean strength -91.0 dBm
gain  -92 dB: sent 50, received 44, loss  12.0 %, mean strength -91.7 dBm
gain  -93 dB: sent 50, received 17, loss  66.0 %, mean strength -92.1 dBm
gain  -94 dB: sent 50, received  0, loss 100.0 %, mean strength none

Observations. Noise trace casino-lab.txt, its first 100 readings, a steady floor of about minus 98 dBm; 50 packets at each gain; seed 1.

Gain (dB)SentReceivedLoss (per cent)Mean strength (dBm)
-8050500.0-80.0
-9150500.0-91.0
-92504412.0-91.7
-93501766.0-92.1
-94500100.0none received

Result. The link lost nothing down to a gain of minus 91 dB and everything at minus 94 dB, and the whole fall came within three decibels: 12 per cent lost at minus 92 dB and 66 per cent at minus 93 dB. That is the steep part of TOSSIM's reception curve, reached as the signal comes within a few decibels of the noise floor. The mean strength equalled the gain on a good link and crept above it near the cliff (minus 91.7 dBm at a gain of minus 92 dB), because the strength reported is signal plus noise.

Why that answer would score well. Every file is there, the build line and the output are shown, the gains were chosen around the cliff rather than spread evenly, the table has units, and the result gives the numbers and the reason. An answer that ran one gain, or five gains all on the good side of the cliff, would have shown no packet loss to analyse.

munotes.in224

The Two-Hour Paper: Sitting the Examination

Q.2, worked: a Module 2 question

Q.2 Measure throughput, packet delivery ratio and end-to-end delay for different MANET routing protocols. (15 marks)

Aim. To measure the throughput, packet delivery ratio and average end-to-end delay of AODV, DSR and DSDV on the same mobile network with the same traffic.

Theory. AODV and DSR are reactive: they look for a route only when a packet needs one, and keep the packet while they look. DSDV is proactive: every node keeps a route to every other node from tables its neighbours broadcast, so a packet either finds a route ready or waits in a short queue. Throughput is the data bits delivered each second while the traffic flows; the packet delivery ratio is the data packets delivered divided by those sent; the average end-to-end delay is the mean, over delivered packets only, of the time received minus the time sent. To compare protocols fairly, the network, the movement and the traffic must be the same for all three, so the movement comes from a seeded generator.

Procedure.

  1. Write one Tcl script for ten mobile nodes and three CBR flows over UDP that takes the routing protocol as its argument, with the movement drawn from a seeded generator.
  2. Write an awk script that reads the trace's agent-level s and r lines for CBR packets and prints the three measures.
  3. Run the script once for each of AODV, DSR and DSDV, measuring each trace.
  4. Tabulate the three measures for the three protocols, and find where the undelivered packets went.

Program.

$ mkdir ~/Q2
$ cd ~/Q2

q2.tcl:

# q2.tcl: ten mobile nodes and three CBR flows, routed by the protocol named on the command line
#   ns q2.tcl <AODV|DSR|DSDV>
set val(chan)   Channel/WirelessChannel
set val(prop)   Propagation/TwoRayGround
set val(netif)  Phy/WirelessPhy
set val(mac)    Mac/802_11
set val(ifq)    Queue/DropTail/PriQueue
set val(ll)     LL
set val(ant)    Antenna/OmniAntenna
set val(ifqlen) 50
set val(nn)     10
set val(rp)     [lindex $argv 0]
set val(x)      800
set val(y)      400
set val(stop)   60.0
if {$val(rp) == "DSR"} {
    set val(ifq) CMUPriQueue
}

set ns [new Simulator]
set tf [open q2.tr w]
$ns trace-all $tf

set topo [new Topography]
$topo load_flatgrid $val(x) $val(y)
create-god $val(nn)

$ns node-config -adhocRouting $val(rp) -llType $val(ll) -macType $val(mac) \
    -ifqType $val(ifq) -ifqLen $val(ifqlen) -antType $val(ant) \
    -propType $val(prop) -phyType $val(netif) -channel [new $val(chan)] \
    -topoInstance $topo -agentTrace ON -routerTrace ON -macTrace OFF \
    -movementTrace OFF

# positions and moves from a seeded generator: the same network for every protocol
set rng [new RNG]
$rng seed 7
for {set i 0} {$i < $val(nn)} {incr i} {
    set node($i) [$ns node]
    $node($i) random-motion 0
    $node($i) set X_ [$rng uniform 0 $val(x)]
    $node($i) set Y_ [$rng uniform 0 $val(y)]
    $node($i) set Z_ 0
}
# every 10 s each node heads for a new random point at 1 to 10 m/s
for {set i 0} {$i < $val(nn)} {incr i} {
    for {set t 0} {$t < $val(stop)} {incr t 10} {
        $ns at $t "$node($i) setdest [$rng uniform 0 $val(x)] [$rng uniform 0 $val(y)] [$rng uniform 1 10]"
    }
}

# three flows, a 512-byte packet every quarter second each, from 5 s to 55 s
foreach {src dst} {0 5 2 7 4 9} {
    set udp [new Agent/UDP]
    $ns attach-agent $node($src) $udp
    set sink [new Agent/Null]
    $ns attach-agent $node($dst) $sink
    $ns connect $udp $sink
    set cbr [new Application/Traffic/CBR]
    $cbr set packetSize_ 512
    $cbr set interval_ 0.25
    $cbr attach-agent $udp
    $ns at [expr 5.0 + $src * 0.01] "$cbr start"
    $ns at 55.0 "$cbr stop"
}

$ns at $val(stop) "finish"
proc finish {} {
    global ns tf
    $ns flush-trace
    close $tf
    exit 0
}
$ns run
munotes.in225

The Two-Hour Paper: Sitting the Examination

Three details decide whether it runs and whether it is fair. DSR gets CMUPriQueue: with the default queue, ns-2.35's DSR crashes (Practical 13). create-god comes before the first node, or the first $ns node stops the run. And one generator, seeded with 7, makes both the starting positions and every move, so all three protocols meet the same network: the positions and move commands are drawn while the script is read, before any routing runs.

measure.awk. Sent and received are counted at the agents (AGT), where a packet starts and ends its journey; the routers' lines count every hop. Throughput divides by 50 s, the time the flows run:

# measure.awk: the CBR packets' delivery ratio, throughput and average end-to-end delay,
# and how many of the delivered packets took longer than a second
$1 == "s" && $4 == "AGT" && $7 == "cbr" { sent++; start[$6] = $2 }
$1 == "r" && $4 == "AGT" && $7 == "cbr" { got++; d = $2 - start[$6]; delay += d; if (d > 1) late++ }
END { printf "%-4s  sent %d  delivered %d  PDR %.1f %%  throughput %.1f kbit/s  delay %.1f ms  over 1 s %d\n",
      proto, sent, got, got * 100 / sent, got * 512 * 8 / 50 / 1000, delay / got * 1000, late }

Output.

$ for p in AODV DSR DSDV; do ns q2.tcl $p > /dev/null 2>&1; awk -v proto=$p -f measure.awk q2.tr; done
AODV  sent 600  delivered 538  PDR 89.7 %  throughput 44.1 kbit/s  delay 768.6 ms  over 1 s 75
DSR   sent 600  delivered 564  PDR 94.0 %  throughput 46.2 kbit/s  delay 2015.1 ms  over 1 s 122
DSDV  sent 600  delivered 430  PDR 71.7 %  throughput 35.2 kbit/s  delay 7.2 ms  over 1 s 0
munotes.in226

The Two-Hour Paper: Sitting the Examination

The average delays run from 7.2 ms to 2015.1 ms, and a result has to say why. Where the undelivered packets went is one more line, counting the trace's drop lines (D) for CBR packets by the layer and the reason ns-2 records:

$ for p in AODV DSR DSDV; do ns q2.tcl $p > /dev/null 2>&1; echo $p; awk '$1 == "D" && $7 == "cbr" { print $4, $5 }' q2.tr | sort | uniq -c; done
AODV
      2 RTR CBK
     60 RTR NRTE
DSR
      2 RTR NRTE
     34 RTR TOUT
DSDV
     56 IFQ ARP
     54 RTR CBK
     60 RTR IFQ

The reasons are ns-2's own codes (trace/cmu-trace.h): NRTE no route, TOUT expired while waiting, IFQ at the router a full queue, CBK handed back by the MAC because the next hop could not be reached, and ARP dropped by ARP. The drops account for every lost packet: 60 + 2 = 62 and 600 - 538 = 62 for AODV, 34 + 2 = 36 and 600 - 564 = 36 for DSR, 56 + 54 + 60 = 170 and 600 - 430 = 170 for DSDV.

Observations. Ten nodes in 800 m by 400 m, new random destinations every 10 s at 1 to 10 m/s, seed 7; three flows of 512-byte packets, four a second each, from 5 s to 55 s, which offers 3 × 4 × 512 × 8 = 49152 bits, about 49.2 kbit, a second.

ProtocolSentDeliveredPDR (per cent)Throughput (kbit/s)Average delay (ms)Delivered after over 1 sLost, and why
AODV60053889.744.1768.67560 no route, 2 MAC callback
DSR60056494.046.22015.112234 expired, 2 no route
DSDV60043071.735.27.2060 queue full, 56 ARP, 54 MAC callback

Result. On the same network with the same traffic, DSR delivered 94.0 per cent of the packets, AODV 89.7 and DSDV 71.7, and throughput followed the delivery ratio, 46.2, 44.1 and 35.2 kbit/s of the 49.2 offered, because the traffic offered was the same. The average delay ran the other way, 7.2 ms for DSDV against 768.6 for AODV and 2015.1 for DSR. The reactive protocols kept packets while they searched for routes: 75 of AODV's delivered packets and 122 of DSR's took over a second, and DSR's 34 losses expired in its send buffer, which keeps a packet up to 30 s (SEND_TIMEOUT, dsr/dsragent.h). DSDV kept almost nothing waiting: it holds only 5 packets for a destination it has no route to (MAX_QUEUE_LENGTH, dsdv/dsdv.h), and 60 were dropped from that full queue. So DSDV's low delay is not speed: it is the delay of the packets it did not drop.

munotes.in227

The Two-Hour Paper: Sitting the Examination

Why that answer would score well. It measures all three things the question names, for three protocols on one seeded network, and the table puts them side by side with units. And it explains the one surprising number, a delay nearly three hundred times larger for DSR than for DSDV, from its own trace, where "DSDV is faster" would have been the wrong conclusion.

If the question is another exercise

In Module 1, nine of the ten exercises are TinyOS applications, and the answer has the same kinds of file as Q.1 above: the module, the configuration, usually a header, the Makefile and the Python script that drives TOSSIM. Practical 8 is built with make micaz sim-sf instead, so that a program on the PC can read the mote through TOSSIM's own SerialForwarder. Practical 1 is the exception: it is a study, and the answer is the block diagram of a sensor node, the figures of real nodes from their datasheets, and the memory a real build uses.

In Module 2, Practicals 11 to 14 are NS-2 scripts and awk, like Q.2. Practicals 15, 16 and 17 are in two halves: a short Python program that implements the mechanism (csma.py, slots.py, dutycycle.py) and an NS-2 run that measures it; write both, with both outputs. Practicals 18 and 19 are Python programs, because NS-2.35 has one antenna class and coverage is geometry: the program and its printout are the answer. Practical 20 is Packet Tracer's procedure, written and marked as not run if the lab has no Packet Tracer, and the namespace network with its capture.

In every case the seven parts are the same, and the observation table is what the question's verb asks for: analyse, measure, compare or evaluate.

The journal, which decides whether you sit the paper at all

Before the examination, not on the day:

  • [ ] All twenty practicals written up, and at the very least sixteen.
  • [ ] Every one signed by your subject teacher, with its date.
  • [ ] Each write-up has all its parts: aim, theory, procedure, every file of the program, output, observations, result.
  • [ ] Module 1 write-ups have every file, including the Makefile and the Python driver; Module 2 write-ups have the awk script as well as the Tcl.
  • [ ] The index page lists all twenty in MU's order, with page numbers and a tick for each signature.
  • [ ] Your name, roll number, class and subject on the cover.
munotes.in228

The Two-Hour Paper: Sitting the Examination

An unsigned journal is not a certified journal, and MU's rule is that a certified journal is compulsory for appearing. Get each practical signed in the week you do it.

The things that lose marks in the hall

No output. If the build fails or the simulation will not run, copy the first error line the machine printed, write one sentence saying what you think it means, and move on.

No observation table. Nearly every exercise in this paper says analyse, measure, compare or evaluate, and the table is the answer to that word.

A Module 1 answer with a file missing. Without the configuration, the module or the Makefile it does not build; without the Python script it does not run.

make micaz instead of make micaz sim. The first builds for the real mote; TOSSIM needs the second (and Practical 8 needs sim-sf).

An unseeded run. TOSSIM chooses a new seed on every run unless it is given one: without t.randomSeed(1), Q.1's script received 18, 13, 21 and 20 packets at minus 93 dB in four runs, where with it the answer is 17 every time. An examiner who runs your answer should get the numbers you wrote. In NS-2, seed every generator you create, and keep a movement file setdest wrote rather than making a new one: setdest seeds itself from the clock (Practical 11).

DSR with the default queue. It crashes. Give DSR CMUPriQueue.

Counting the router's lines as packets sent and received. A packet forwarded over three hops appears at three routers. Sent and received are counted at the agents, AGT.

Delay averaged over every packet sent, or throughput over the whole run. Delay is averaged over delivered packets; throughput is divided by the time the traffic ran.

Ninety minutes on one question. Half the marks are in the other laboratory.

No result. Two or three lines with the numbers in them. They are the easiest marks on the paper.

Procedure

  1. A week before: count your write-ups, make sure at least sixteen are signed, and complete the index.
  2. Re-read the first chapter and this one, and chapters 2 and 13 for the two laboratories.
  3. Work one question from each module against the clock, with no notes, and see how long you actually take.
  4. In the hall: read both questions, decide the order, and write the seven headings on the answer sheet.
  5. Do the question you are surer of first; type the short files first and build early; stop at the hour.
  6. Write the output into the answer as soon as you have it, before improving anything.
  7. Fill in the observation table while the output is still on the screen.
  8. Leave ten minutes at the end to check that both answers have output, a table and a result.
munotes.in229

The Two-Hour Paper: Sitting the Examination

Observations

Measured, on the two worked answersValue
Q.1, loss at gains of -80, -91, -92, -93 and -94 dB0, 0, 12, 66 and 100 per cent of 50 packets
Q.1, mean strengthequal to the gain on a good link, -91.7 dBm at a gain of -92 dB
Q.1, the buildabout 4 seconds on the lab machine
Q.2, delivery ratio: DSR, AODV, DSDV94.0, 89.7 and 71.7 per cent of 600 packets
Q.2, throughput46.2, 44.1 and 35.2 kbit/s of 49.2 offered
Q.2, average delay2015.1, 768.6 and 7.2 ms
Q.2, delivered after more than a second122, 75 and 0 packets
Q.2, all three runsunder 2 seconds on the lab machine

Result

A complete Semester End Practical Examination was worked under MU's printed conditions: two hours, two questions of fifteen marks, one from each module. Q.1 built a two-mote TinyOS application for TOSSIM and found the link perfect down to a gain of minus 91 dB and dead at minus 94 dB, with 12 and 66 per cent lost in between, the reported strength following the gain. Q.2 ran AODV, DSR and DSDV on one seeded ten-node network and measured delivery ratios of 89.7, 94.0 and 71.7 per cent, throughputs of 44.1, 46.2 and 35.2 kbit/s, and average delays of 768.6, 2015.1 and 7.2 ms, and explained the delays from the trace: the reactive protocols delivered packets that had waited for routes, and DSDV dropped packets rather than hold them. Both answers were written in the seven-part form, and the journal requirements for being allowed to sit the paper were listed.

Where marks are lost

Everything in "The things that lose marks in the hall" above, and one more that begins before the hall: a journal short of sixteen signed practicals, or not signed at all, which decides whether you sit the paper before any question is read.

For the journal

This chapter is not one of the twenty practicals and does not go into the journal. Use its checklist on the journal itself, a week before the examination.

Quick revision

  • Two hours, 30 marks: Q.1 on Module 1 for 15, Q.2 on Module 2 for 15.
  • A certified journal is compulsory, and at least 16 of the 20 practicals must be completed.
  • Seven parts: aim, theory, procedure, program, output, observations, result. Write the headings first.
  • Start with the question you are surer of, and stop at the hour.
  • Module 1: type the Makefile and the configuration first, build as soon as the module is in, make micaz sim.
  • Module 2: the thirty-line skeleton, then nodes, movement, traffic and finish; DSR needs CMUPriQueue; create-god before the first node.
  • Seed every run: t.randomSeed in TOSSIM (without it the counts change from run to run), a seeded RNG in NS-2.
  • Count sent and received at AGT; average delay over delivered packets; throughput over the time the traffic ran.
  • A mean can hide a long tail: count the packets that waited, and read the drop reasons.
  • Never leave the output blank.
munotes.in230

The Two-Hour Paper: Sitting the Examination

Questions you must be able to answer

1. How long is the paper and how is it divided? Two hours and 30 marks: one practical question based on Module 1 for 15 marks and one based on Module 2 for 15.

2. What do you need in order to be allowed to sit it? A certified journal, and at least eighty per cent of the practicals completed, which is sixteen of the twenty.

3. In Q.1, why were the gains chosen as minus 80, 91, 92, 93 and 94 dB, and not spread evenly? Because the loss changes from none to total within about three decibels. One gain well inside the good range shows the working link; the rest are packed around the cliff, where the loss can be analysed.

4. Why is the mean strength at a gain of minus 92 dB reported as minus 91.7 dBm? The strength TOSSIM reports is signal plus noise. On a strong link the noise is too small to matter; near the cliff it is only a few decibels below the signal and raises the sum.

5. Your TinyOS build fails with ten minutes left. What do you write? The aim, the theory, every file as far as you have it, the first error line the build printed, and one sentence saying what you think it means. Never an empty output.

6. In Q.2, how do you make the comparison between protocols fair? The same nodes, the same movement and the same traffic for every protocol, from one seeded generator, and only the routing protocol changed between runs.

7. Why are sent and received counted on AGT lines? The agent is where a packet starts and ends. The router's lines are written at every hop, so counting them counts one packet several times.

8. DSDV had the lowest average delay and the lowest delivery ratio. Is it the best protocol here? No. Its delay is averaged over the packets it delivered, and it delivered them only when it already had a route; with no route it held at most five packets per destination and dropped the rest. AODV and DSR kept packets while they searched, delivering more, some of them after long waits.

munotes.in231

The Two-Hour Paper: Sitting the Examination

9. What does TOUT in DSR's drop lines mean? The packet expired in DSR's send buffer: it waited for a route for longer than the buffer keeps a packet, 30 s in ns-2.35.

10. How much of your two hours should the first question take? About an hour. If it has taken more, write down what you have and move on: the other fifteen marks are in the other laboratory.

Contents This chapter on its own page

munotes.in232

The rest of this subject

These notes are cut from the University's printed syllabus. Open the syllabus itself for the same subject.

Issue
Done!