21.08.2013 Views

A State-Based Programming Model for Wireless Sensor Networks

A State-Based Programming Model for Wireless Sensor Networks

A State-Based Programming Model for Wireless Sensor Networks

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

Diss. ETH Nr. 17397<br />

A <strong>State</strong>-<strong>Based</strong> <strong>Programming</strong> <strong>Model</strong><br />

<strong>for</strong> <strong>Wireless</strong> <strong>Sensor</strong> <strong>Networks</strong><br />

A dissertation submitted to the<br />

SWISS FEDERAL INSTITUTE OF TECHNOLOGY ZURICH<br />

(ETH ZURICH)<br />

<strong>for</strong> the degree of<br />

Doctor of Sciences<br />

presented by<br />

OLIVER KASTEN<br />

Dipl.-In<strong>for</strong>matiker, TU Darmstadt<br />

born July 10, 1969<br />

citizen of Germany<br />

accepted on the recommendation of<br />

Prof. Dr. F. Mattern, examiner<br />

Prof. Dr. L. Thiele, co-examiner<br />

2007


Abstract<br />

<strong>Sensor</strong> nodes are small, inexpensive, and programmable devices that combine<br />

an autonomous power supply with computing, sensing, and wireless communication<br />

capabilities. <strong>Networks</strong> of sensor nodes can be deployed in the environment<br />

at a large scale to unobtrusively monitor phenomena of the real world.<br />

<strong>Wireless</strong> sensor networks are an emerging field of research with many potential<br />

applications. So far, however, only few applications have actually been realized.<br />

This is in part due to the lack of appropriate programming support, which<br />

makes the development of sensor-network applications tedious and error prone.<br />

This dissertation contributes a novel programming model and development environment<br />

<strong>for</strong> the efficient, modular, and well structured programming of wireless<br />

sensor nodes.<br />

Today there are two principal programming models used <strong>for</strong> sensor nodes,<br />

the multi-threaded model and the event-driven model. The multi-threaded model<br />

requires system support that is often considered too heavy <strong>for</strong> sensor nodes that<br />

operate at the low end of the resource spectrum. To cope with this issue, the<br />

event-driven model has been proposed. It requires very little runtime support<br />

by the system software and can thus be implemented even on the most constrained<br />

sensor nodes.<br />

The simple and lightweight approach to system software, however, tends to<br />

make event-driven applications in turn quite memory inefficient: Since the eventdriven<br />

model limits the use of local variables, programmers need to store temporary<br />

data in global variables. The memory of global variables, however, cannot<br />

easily and automatically be reused, hence the memory inefficiency. To counter<br />

this effect, programmers can resort to manual memory management, though<br />

this significantly affects program correctness and code modularity. In addition<br />

to its drawback of memory inefficiency, event-driven programming requires developers<br />

to manually keep track of the current program-state, which makes code<br />

modularization and debugging difficult, and leads to unstructured code.<br />

The key contribution of this dissertation is to show that the inadequacies of<br />

the event-driven model can be remedied without impairing its positive aspects,<br />

particularly its memory-efficient realization in sensor-node system software.<br />

Concretely, we present the Object <strong>State</strong> <strong>Model</strong> (OSM), a programming model<br />

that extends the event-driven programming paradigm with a notion of hierarchical<br />

and concurrent program states. Our thesis is that such a state-based model<br />

allows to specify well-structured, modular, and memory-efficient programs, yet requires<br />

as few runtime-resources as the event-driven model. To support this claim, we also<br />

present a programming environment based on the OSM model (including a programming<br />

language and compiler), as well as a sensor-node operating system<br />

capable of executing OSM programs.


ii<br />

The main idea behind OSM is to explicitly model sensor-node programs as<br />

state machines, where variables are associated with states and computational<br />

operations are associated with state transitions. In OSM, states serve three purposes.<br />

Firstly, states are used as scoping mechanism <strong>for</strong> variables. The scope and<br />

lifetime of variables attached to a state is confined to that state and all of its substates.<br />

The memory <strong>for</strong> storing a state’s variables is automatically reclaimed by<br />

the runtime system as the program leaves the corresponding state. <strong>State</strong> variables<br />

can be thought of as the local variables of OSM. As such they represent<br />

a great advancement over event-driven programming, where the majority of<br />

variables effectively have global scope and lifetime. By modeling temporary<br />

data with state variables, the use of OSM can significantly increase a program’s<br />

memory efficiency.<br />

Secondly, the explicit notion of program states allows to model the structure<br />

and control flow of a program on a high abstraction level. Specifically, programs<br />

can be initially specified in terms of coarse modules (i.e., states), which<br />

can be subsequently refined (with substates), leading to modular and readable<br />

program code. The third purpose of states is to provide a context <strong>for</strong> computational<br />

operations. <strong>State</strong>s clearly define which variables are visible, and at what<br />

point in the control flow the program resides when an operation is executed.<br />

In the event-driven model, in contrast, the program’s context has to be maintained<br />

manually, which typically constitutes a significant fraction of the code,<br />

thus making the program hard to read and error prone.<br />

The OSM programming language captures the three concepts described above<br />

in order to foster memory efficient, modular, and well-structured programs. A<br />

compiler <strong>for</strong> the proposed language trans<strong>for</strong>ms state-based OSM programs back<br />

into an event-driven program notation, adding code <strong>for</strong> automatic memorymanagement<br />

of state variables and code <strong>for</strong> automatic control-flow management.<br />

The compiler-generated code is very lean and does not impose additional<br />

requirements on the system software, such as dynamic memory management.<br />

Rather, the trans<strong>for</strong>med programs are directly executable on our event-driven<br />

system software <strong>for</strong> resource-constrained sensor nodes. Our language, compiler,<br />

and sensor-node system software <strong>for</strong>m the basis of our thesis and constitute a<br />

complete state-based programming environment <strong>for</strong> resource-constrained sensor<br />

nodes based on OSM.


Kurzfassung<br />

Drahtlose <strong>Sensor</strong>knoten sind kostengünstige, programmierbare und vernetzte<br />

Kleinstcomputer, die <strong>Sensor</strong>ik, drahtlose Kommunikation sowie Energieversorgung<br />

in sich vereinen. Im grossen Massstab eingesetzt können drahtlose Netze<br />

aus solchen <strong>Sensor</strong>knoten Phänomene der realen Welt unauffällig beobachten.<br />

Drahtlose <strong>Sensor</strong>netze sind ein relativ neues Forschungsfeld mit vielen potentiellen<br />

Anwendungen. Unter anderem auf Grund des Fehlens geeigneter Unterstützung<br />

bei der Programmierung von <strong>Sensor</strong>knoten sind von diesen Anwendungen<br />

jedoch erst die wenigsten realisiert worden. Diese Dissertation liefert<br />

ein neuartiges Programmiermodell mit dazugehöriger Entwicklungsumgebung<br />

als Beitrag zur effizienten, modularen und wohlstrukturierten Programmierung<br />

von <strong>Sensor</strong>knoten.<br />

Heutzutage werden zur Programmierung von Drahtlosen <strong>Sensor</strong>knoten im<br />

Wesentlichen zwei <strong>Model</strong>le verwendet: das Multi-Threading <strong>Model</strong>l und das ereignisbasierte<br />

<strong>Model</strong>l. Das Multi-Threading <strong>Model</strong>l er<strong>for</strong>dert Systemunterstützung,<br />

die häufig als zu schwergewichtig empfunden wird, insbesondere für<br />

<strong>Sensor</strong>knoten die am unteren Ende des Ressourcenspektrums betrieben werden.<br />

Um solchen Ressourcenbeschränkungen gerecht zu werden, wurde das ereignisbasierte<br />

Programmiermodell vorgeschlagen. Dieses <strong>Model</strong>l er<strong>for</strong>dert sehr geringe<br />

Systemunterstützung und kann deshalb auch auf sehr beschränkten <strong>Sensor</strong>en<br />

eingesetzt werden.<br />

Währenddessen jedoch die Systemunterstützung für ereignisbasierte Programme<br />

nur wenige Ressourcen er<strong>for</strong>dert, sind ereignisbasierte Programme<br />

selbst sehr speicherineffizient. Das ereignisbasierte <strong>Model</strong>l verhindert nämlich<br />

die effiziente Nutzung von lokalen Variablen. Als Konsequenz daraus müssen<br />

Programmierer temporäre Daten in globalen Variablen speichern, wodurch die<br />

effiziente und automatische Wiederverwendung von Variablenspeicher verhindert<br />

wird. Für temporäre Daten könnte zwar der Variablenspeicher auch manuell<br />

wiederverwendet werden. Das schwört jedoch Probleme mit der Modularität<br />

und der Fehleranfälligkeit herauf. Zusätzlich zum Nachteil der Speicherineffizienz<br />

erzwingt das ereignisbasierte <strong>Model</strong>l ein Programmierparadigma,<br />

das Modularisierung und Fehlersuche signifikant erschwert und des Weiteren<br />

zu unstrukturiertem Code führt.<br />

Der wesentliche Beitrag dieser Dissertation ist zu zeigen, dass die genannten<br />

Unzulänglichkeiten des ereignisbasierten <strong>Model</strong>ls behoben werden können, ohne<br />

seine positiven Eigenschaften (also die speichereffiziente Implementierbarkeit<br />

auf <strong>Sensor</strong>knoten) massgeblich zu beeinträchtigen.<br />

Konkret stellen wir das Object <strong>State</strong> <strong>Model</strong> (OSM) vor, ein Programmiermodell<br />

das das ereignisbasierte <strong>Model</strong>l um die Abstraktion von hierarchisch- und<br />

nebenläufig-strukturierbaren Programmzuständen erweitert. Wir vertreten die


iv<br />

These, dass ein solches zustandsbasiertes <strong>Model</strong>l die Spezifikation von klar strukturierten,<br />

modularen und speichereffizienten Programmen erlaubt und dennoch nicht<br />

mehr Ressourcen zu seiner Unterstützung auf Systemebene benötigt als das ereignisbasierte<br />

<strong>Model</strong>l. Zur Abstützung unserer These präsentieren wir eine Entwicklungsumgebung<br />

basierend auf diesem zustandsbasierten Programmiermodell<br />

(bestehend aus Programmiersprache und Compiler) sowie ein Betriebssystem<br />

zur Ausführung von OSM Programmen.<br />

Die wesentliche Idee hinter OSM ist es <strong>Sensor</strong>knoten-Programme explizit als<br />

Zustandsmaschinen zu modellieren, wobei Variablen mit Zuständen assoziiert<br />

werden und Berechnungen mit Zustandsübergängen. Zustände in OSM erfüllen<br />

dann drei Aufgaben. Zum einen dienen sie als Geltungsbereich für Variablen.<br />

Der Geltungsbereich, und damit die Lebensdauer, jeder Variable ist an genau<br />

einen Zustand und dessen Unterzustände gebunden. Wenn das Programm<br />

diesen Zustand verlässt, wird der Speicher aller mit dem Zustand assoziierten<br />

Variablen freigegeben und kann wiederverwendet werden. Solche an Zustände<br />

gebundene Variablen können deshalb als lokale Variablen von OSM betrachtet<br />

werden. Sie stellen eine grosse Errungenschaft gegenüber dem ereignisbasierten<br />

<strong>Model</strong>l dar, in dem die Mehrzahl der Variablen einen globalen Geltungsbereich<br />

haben und ihr Speicher deshalb nicht wiederverwertet werden<br />

kann. Die Spezifikation von temporären Daten mit Zustandsvariablen von OSM<br />

kann die Speichereffizienz einer Anwendung signifikant steigern. Als Zweites<br />

erlauben es explizite Zustände die Struktur und den Kontrollfluss eines Programmes<br />

auf hohem Abstraktionsniveau zu beschreiben. Insbesondere können<br />

Programme zuerst grob und mit wenigen Zuständen beschrieben werden, die<br />

im Weiteren durch Einfügen von Unterzuständen schrittweise verfeinert werden.<br />

Dieses Vorgehen führt zu einem modularem Programmaufbau und gut<br />

lesbarem Programm-Code. Zuguterletzt definieren explizite Programmzustände<br />

einen Kontext für Berechnungen. Zustände definieren klar an welcher Stelle<br />

im Kontrollfluss sich ein Programm befindet, welche Variablen sichtbar sind,<br />

welche Zustände bereits durchlaufen und welche Funktion bereits ausgeführt<br />

wurden. Im ereignisbasierten <strong>Model</strong>l dagegen müssen In<strong>for</strong>mationen zum Programmkontext<br />

manuell vom Programmierer verwaltet werden. Typischerweise<br />

macht diese manuelle Zustandsverwaltung einen grossen Teil des gesamten<br />

Programm-Codes aus, was sich in schlechter Lesbarkeit und hoher Fehleranfälligkeit<br />

niederschlägt.<br />

Durch die oben beschriebenen Konzepte erleichtert OSM die Beschreibung<br />

von speichereffizienten, modularen, wohlstrukturierten und lesbaren Programmen.<br />

Der Compiler für die vorgeschlagene Sprache überführt zustandsbasierte<br />

OSM-Programme zurück in eine ereignisbasierte Repräsentation. Dabei wird<br />

Code zur automatischen Speicherverwaltung von Variablen und zur Verwaltung<br />

des Kontrollflusses hinzugefügt. Der vom Compiler erzeugte Code ist<br />

leichtgewichtig und er<strong>for</strong>dert keine weitere Unterstützung auf Systemebene,<br />

wie z.B. dynamisch Speicherverwaltung. Vielmehr sind die erzeugten Programme<br />

direkt auf unserem ereignisbasiertem Betriebssystem für ressourcenbeschränkte<br />

<strong>Sensor</strong>knoten ausführbar. Die von uns entwickelte vollständige<br />

Programmierumgebung für <strong>Sensor</strong>knoten, bestehend aus Programmiersprache,<br />

Compiler, und Betriebssystem, zeigt dies exemplarisch und bildet somit die<br />

Grundlage für diese Dissertation.


Contents<br />

1 Introduction 1<br />

1.1 Background . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1<br />

1.2 Motivation and Problem <strong>State</strong>ment . . . . . . . . . . . . . . . . . . 2<br />

1.3 Thesis <strong>State</strong>ment . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3<br />

1.4 Contributions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3<br />

1.4.1 Modular and Well-Structured Design . . . . . . . . . . . . 4<br />

1.4.2 Automated <strong>State</strong> Management . . . . . . . . . . . . . . . . 4<br />

1.4.3 Memory-Efficient <strong>State</strong> Variables . . . . . . . . . . . . . . . 4<br />

1.4.4 Light-Weight Execution Environment . . . . . . . . . . . . 5<br />

1.4.5 Evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5<br />

1.5 Thesis Organization . . . . . . . . . . . . . . . . . . . . . . . . . . . 5<br />

2 <strong>Wireless</strong> <strong>Sensor</strong> <strong>Networks</strong> 7<br />

2.1 WSN Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . 8<br />

2.1.1 Environmental Observation . . . . . . . . . . . . . . . . . . 8<br />

2.1.2 Wildlife and Farm-Animal Monitoring . . . . . . . . . . . 9<br />

2.1.3 Intelligent Environments . . . . . . . . . . . . . . . . . . . 10<br />

2.1.4 Facility Management . . . . . . . . . . . . . . . . . . . . . . 10<br />

2.1.5 Logistics and Asset Tracking . . . . . . . . . . . . . . . . . 11<br />

2.1.6 Military . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11<br />

2.2 WSN Characteristics . . . . . . . . . . . . . . . . . . . . . . . . . . 12<br />

2.2.1 Deployment and Environmental Integration . . . . . . . . 12<br />

2.2.2 Size, Weight, and Cost . . . . . . . . . . . . . . . . . . . . . 12<br />

2.2.3 Limited Energy Budget . . . . . . . . . . . . . . . . . . . . 12<br />

2.2.4 Lifetime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13<br />

2.2.5 Limited Computing Resources . . . . . . . . . . . . . . . . 13<br />

2.2.6 Collaboration . . . . . . . . . . . . . . . . . . . . . . . . . . 14<br />

2.2.7 Back-End Connectivity . . . . . . . . . . . . . . . . . . . . . 14<br />

2.2.8 Mobility and Network Dynamics . . . . . . . . . . . . . . . 15<br />

2.2.9 Bursty Traffic . . . . . . . . . . . . . . . . . . . . . . . . . . 15<br />

2.2.10 Dynamic Role Assignment . . . . . . . . . . . . . . . . . . 15<br />

2.2.11 Node Heterogeneity . . . . . . . . . . . . . . . . . . . . . . 16<br />

2.3 <strong>Sensor</strong> Nodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16<br />

2.3.1 Device Classes . . . . . . . . . . . . . . . . . . . . . . . . . 16<br />

2.3.2 <strong>Sensor</strong>-Node Components . . . . . . . . . . . . . . . . . . . 19<br />

2.3.3 Selected <strong>Sensor</strong>-Node Hardware Plat<strong>for</strong>ms . . . . . . . . . 23<br />

2.4 Embedded Systems . . . . . . . . . . . . . . . . . . . . . . . . . . . 27<br />

2.4.1 Characteristics of Embedded Systems . . . . . . . . . . . . 27<br />

2.4.2 Diversity of Embedded Systems . . . . . . . . . . . . . . . 28


vi Contents<br />

2.4.3 <strong>Wireless</strong> <strong>Sensor</strong> Nodes . . . . . . . . . . . . . . . . . . . . . 28<br />

2.5 Summary and Outlook . . . . . . . . . . . . . . . . . . . . . . . . . 28<br />

3 <strong>Programming</strong> and Runtime Environments 31<br />

3.1 System Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . 32<br />

3.1.1 Resource Efficiency . . . . . . . . . . . . . . . . . . . . . . . 33<br />

3.1.2 Reliability . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33<br />

3.1.3 Reactivity . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34<br />

3.2 <strong>Programming</strong> <strong>Model</strong>s . . . . . . . . . . . . . . . . . . . . . . . . . 34<br />

3.2.1 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35<br />

3.2.2 The Control Loop . . . . . . . . . . . . . . . . . . . . . . . . 35<br />

3.2.3 Event-driven <strong>Programming</strong> . . . . . . . . . . . . . . . . . . 36<br />

3.2.4 Multi-Threaded <strong>Programming</strong> . . . . . . . . . . . . . . . . 38<br />

3.3 Memory Management . . . . . . . . . . . . . . . . . . . . . . . . . 42<br />

3.3.1 Resource Issues . . . . . . . . . . . . . . . . . . . . . . . . . 42<br />

3.3.2 Reliability Concerns . . . . . . . . . . . . . . . . . . . . . . 42<br />

3.3.3 Dynamic Memory Management <strong>for</strong> <strong>Sensor</strong> Nodes . . . . . 43<br />

3.4 Process <strong>Model</strong>s . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44<br />

3.4.1 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44<br />

3.4.2 Combinations of Processes <strong>Model</strong>s . . . . . . . . . . . . . . 44<br />

3.4.3 Over-the-Air Reprogramming . . . . . . . . . . . . . . . . 45<br />

3.4.4 Process Concurrency . . . . . . . . . . . . . . . . . . . . . . 46<br />

3.4.5 Analysis of Process <strong>Model</strong>s . . . . . . . . . . . . . . . . . . 49<br />

3.5 Overview and Examples of <strong>State</strong>-of-the-Art Operating Systems . 50<br />

3.5.1 The BTnode System Software . . . . . . . . . . . . . . . . . 51<br />

3.5.2 TinyOS and NesC . . . . . . . . . . . . . . . . . . . . . . . . 56<br />

3.6 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59<br />

4 Event-driven <strong>Programming</strong> in Practice 61<br />

4.1 Limitations of Event-Driven <strong>Programming</strong> . . . . . . . . . . . . . 62<br />

4.1.1 Manual Stack Management . . . . . . . . . . . . . . . . . . 64<br />

4.1.2 Manual <strong>State</strong> Management . . . . . . . . . . . . . . . . . . 64<br />

4.1.3 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67<br />

4.2 The Anatomy of <strong>Sensor</strong>-Node Programs . . . . . . . . . . . . . . . 68<br />

4.2.1 Characteristics of a Phase . . . . . . . . . . . . . . . . . . . 68<br />

4.2.2 Sequential Phase Structures . . . . . . . . . . . . . . . . . . 69<br />

4.2.3 Concurrency . . . . . . . . . . . . . . . . . . . . . . . . . . . 76<br />

4.2.4 Sequential vs. Phase-<strong>Based</strong> <strong>Programming</strong> . . . . . . . . . . 79<br />

4.3 Extending the Event <strong>Model</strong>: a <strong>State</strong>-<strong>Based</strong> Approach . . . . . . . 79<br />

4.3.1 Automatic <strong>State</strong> Management . . . . . . . . . . . . . . . . . 80<br />

4.3.2 Automatic Stack Management . . . . . . . . . . . . . . . . 80<br />

5 The Object-<strong>State</strong> <strong>Model</strong> 83<br />

5.1 Basic <strong>State</strong>chart Concepts . . . . . . . . . . . . . . . . . . . . . . . 84<br />

5.2 Flat OSM <strong>State</strong> Machines . . . . . . . . . . . . . . . . . . . . . . . . 85<br />

5.2.1 <strong>State</strong> Variables . . . . . . . . . . . . . . . . . . . . . . . . . . 85<br />

5.2.2 Transitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85<br />

5.2.3 Actions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86<br />

5.3 Progress of Time: Machine Steps . . . . . . . . . . . . . . . . . . . 86


Contents vii<br />

5.3.1 Non-determinism . . . . . . . . . . . . . . . . . . . . . . . . 87<br />

5.3.2 Processing <strong>State</strong> Changes . . . . . . . . . . . . . . . . . . . 89<br />

5.3.3 No Real-Time Semantics . . . . . . . . . . . . . . . . . . . . 89<br />

5.4 Parallel Composition . . . . . . . . . . . . . . . . . . . . . . . . . . 90<br />

5.4.1 Concurrent Events . . . . . . . . . . . . . . . . . . . . . . . 90<br />

5.4.2 Progress in Parallel Machines . . . . . . . . . . . . . . . . . 91<br />

5.5 Hierarchical Composition . . . . . . . . . . . . . . . . . . . . . . . 91<br />

5.5.1 Superstate Entry . . . . . . . . . . . . . . . . . . . . . . . . 92<br />

5.5.2 Initial <strong>State</strong> Selection . . . . . . . . . . . . . . . . . . . . . . 93<br />

5.5.3 Substate Preemption . . . . . . . . . . . . . . . . . . . . . . 94<br />

5.5.4 Progress in <strong>State</strong> Hierarchies . . . . . . . . . . . . . . . . . 95<br />

5.6 <strong>State</strong> Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96<br />

5.7 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97<br />

6 Implementation 99<br />

6.1 OSM Specification Language . . . . . . . . . . . . . . . . . . . . . 100<br />

6.1.1 <strong>State</strong>s and Flat <strong>State</strong> Machines . . . . . . . . . . . . . . . . 100<br />

6.1.2 Grouping and Hierarchy . . . . . . . . . . . . . . . . . . . . 103<br />

6.1.3 Parallel Composition . . . . . . . . . . . . . . . . . . . . . . 105<br />

6.1.4 Modularity and Code Reuse through Machine Incarnation 105<br />

6.2 OSM Language Mapping . . . . . . . . . . . . . . . . . . . . . . . 107<br />

6.2.1 Variable Mapping . . . . . . . . . . . . . . . . . . . . . . . . 109<br />

6.2.2 Control Structures . . . . . . . . . . . . . . . . . . . . . . . 109<br />

6.2.3 Mapping OSM Control-Flow to Esterel . . . . . . . . . . . 112<br />

6.3 OSM Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115<br />

6.4 OSM System Software . . . . . . . . . . . . . . . . . . . . . . . . . 117<br />

6.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118<br />

7 <strong>State</strong>-based <strong>Programming</strong> in Practice 119<br />

7.1 An Intuitive Motivation . . . . . . . . . . . . . . . . . . . . . . . . 119<br />

7.2 Modular and Well-Structured Program Design . . . . . . . . . . . 121<br />

7.2.1 EnviroTrack Case Study . . . . . . . . . . . . . . . . . . . . 121<br />

7.2.2 Manual versus Automated <strong>State</strong> Management . . . . . . . 123<br />

7.2.3 Resource Initialization in Context . . . . . . . . . . . . . . 126<br />

7.2.4 Avoiding Accidental Concurrency . . . . . . . . . . . . . . 131<br />

7.3 Memory-Efficient Programs . . . . . . . . . . . . . . . . . . . . . . 133<br />

7.3.1 Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134<br />

7.3.2 General Applicability and Efficiency . . . . . . . . . . . . . 136<br />

7.4 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138<br />

8 Related Work 139<br />

8.1 <strong>Programming</strong> Embedded Systems . . . . . . . . . . . . . . . . . . 139<br />

8.1.1 Conventional <strong>Programming</strong> Methods . . . . . . . . . . . . 139<br />

8.1.2 Domain-specific <strong>Programming</strong> Approaches . . . . . . . . . 140<br />

8.1.3 Embedded-Systems <strong>Programming</strong> and <strong>Sensor</strong> Nodes . . . 143<br />

8.2 <strong>State</strong>-<strong>Based</strong> <strong>Model</strong>s of Computation . . . . . . . . . . . . . . . . . 143<br />

8.2.1 <strong>State</strong>charts . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143<br />

8.2.2 UML <strong>State</strong>charts . . . . . . . . . . . . . . . . . . . . . . . . 144<br />

8.2.3 Finite <strong>State</strong> Machines with Datapath (FSMD) . . . . . . . . 144


viii Contents<br />

8.2.4 Program <strong>State</strong> Machines (PSM), SpecCharts . . . . . . . . . 144<br />

8.2.5 Communicating FSMs: CRSM and CFSM . . . . . . . . . . 145<br />

8.2.6 Esterel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145<br />

8.2.7 Functions driven by state machines (Fun<strong>State</strong>) . . . . . . . 146<br />

8.3 <strong>Sensor</strong>-Node <strong>Programming</strong> Frameworks . . . . . . . . . . . . . . 146<br />

8.3.1 TinyOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146<br />

8.3.2 Contiki . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148<br />

8.3.3 Protothreads . . . . . . . . . . . . . . . . . . . . . . . . . . . 148<br />

8.3.4 SenOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149<br />

9 Conclusions and Future Work 151<br />

9.1 Conclusions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151<br />

9.2 Contributions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151<br />

9.2.1 Problem Analysis: Shortcomings of Events . . . . . . . . . 152<br />

9.2.2 Solution Approach: <strong>State</strong>-based <strong>Programming</strong> . . . . . . . 152<br />

9.2.3 Prototypical Implementation . . . . . . . . . . . . . . . . . 153<br />

9.2.4 Evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153<br />

9.3 Limitations and Future Work . . . . . . . . . . . . . . . . . . . . . 153<br />

9.3.1 Language and Program Representation . . . . . . . . . . . 154<br />

9.3.2 Real-Time Aspects of OSM . . . . . . . . . . . . . . . . . . 155<br />

9.3.3 Memory Issues . . . . . . . . . . . . . . . . . . . . . . . . . 155<br />

9.4 Concluding Remarks . . . . . . . . . . . . . . . . . . . . . . . . . . 156<br />

A OSM Code Generation 157<br />

A.1 OSM Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157<br />

A.2 Variable Mapping (C include file) . . . . . . . . . . . . . . . . . . . 159<br />

A.3 Control Flow Mapping–Stage One (Esterel) . . . . . . . . . . . . . 161<br />

A.4 Control Flow Mapping–Stage Two (C) . . . . . . . . . . . . . . . . 164<br />

A.4.1 Output of the Esterel Compiler . . . . . . . . . . . . . . . . 164<br />

A.4.2 Esterel-Compiler Output Optimized <strong>for</strong> Memory Efficiency 169<br />

A.5 Compilation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173<br />

A.5.1 Example Compilation Run . . . . . . . . . . . . . . . . . . 173<br />

A.5.2 Makefile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173<br />

B Implementations of the EnviroTrack Group Management 175<br />

B.1 NesC Implementation . . . . . . . . . . . . . . . . . . . . . . . . . 175<br />

B.2 OSM Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . 180<br />

Bibliography 181


1 Introduction<br />

1.1 Background<br />

Over the past decades, advances in sensor technology, miniaturization and lowcost,<br />

low-power chip design have led to the development of wireless sensor<br />

nodes: small, inexpensive, and programmable devices that combine computing,<br />

sensing, short-range wireless communication, and an autonomous power<br />

supply. This development has inspired the vision of wireless sensor networks—<br />

wireless networks of sensor nodes that can be deployed in the environment at<br />

the large scale to unobtrusively monitor phenomena of the real world. There<br />

is a wide range of applications envisioned <strong>for</strong> such wireless sensor networks,<br />

including precision agriculture, monitoring of fragile goods and building structures,<br />

monitoring the habitat of endangered species and the animals themselves,<br />

emergency response, environmental and micro-climate monitoring, logistics,<br />

and military surveillance.<br />

The most popular programming model <strong>for</strong> developing sensor-network applications<br />

today is the event-driven model. Several programming frameworks<br />

based on that model exist, <strong>for</strong> example, the BTnode system software [21], Contiki<br />

[37], the TinyOS system software [59] and nesC programming language [45],<br />

SOS [52] and Maté [79]. The event-driven model is based on two main abstractions:<br />

events and actions. Events in the context of event-driven programming<br />

can be considered abstractions of real-world events. Events are typically typed<br />

to distinguish different event classes and may contain parameters, <strong>for</strong> example,<br />

to carry associated event data. At runtime, the occurrence of an event can trigger<br />

the execution of a computational action. Typically, there is a one-to-one association<br />

between event types and actions. Then, an event-driven program consists<br />

of as many actions as there are event types. Actions are typically implemented<br />

as functions of a sequential programming language. They always run to completion<br />

without being interrupted by other actions. In order not to monopolize<br />

the CPU <strong>for</strong> any significant time, actions need to be non-blocking. There<strong>for</strong>e, at<br />

any point in the control flow where an operation needs to wait <strong>for</strong> some event<br />

to occur (e.g., a reply message or acknowledgment), the operation must be split<br />

into two parts: a non-blocking operation request and an asynchronous completion<br />

event. The completion event then triggers another action, which continues<br />

the operation.<br />

One of the main strengths of the event-driven model is that its support in the<br />

underlying system software incurs very little memory and runtime overhead.<br />

Also, the event-driven model is simple, yet intuitively captures the reactive nature<br />

of sensor-network applications. In contrast, the multi-threaded programming<br />

model, another programming approach that has been applied to wireless<br />

sensor networks, is typically considered to incur too much memory and computational<br />

overhead (mainly because of per-thread stacks and context switches).


2 Chapter 1. Introduction<br />

This makes the multi-threaded model generally less suitable <strong>for</strong> programming<br />

sensor nodes, which are typically limited in resources, and unsuitable <strong>for</strong> sensor<br />

nodes operating at the low end of the resource spectrum (cf. [37, 59]). As a consequence,<br />

in the context of sensor networks, event-based systems are very popular<br />

with several programming frameworks in existence. These frameworks,<br />

particularly TinyOS and nesC, are supported by a large user base. Hence, many<br />

sensor-node programmers find it natural to structure their programs according<br />

to this paradigm.<br />

1.2 Motivation and Problem <strong>State</strong>ment<br />

While event-driven programming is both simple and efficient, we have found<br />

that it suffers from a very important limitation: it lacks an abstraction of state.<br />

Very often, the behavior of a sensor-node program not only depends on a particular<br />

event—as the event model suggests—but also on the event history and<br />

on computational results of previous actions; in other words: program state.<br />

We use an example to illustrate this point. In the EnviroTrack [6] middleware<br />

<strong>for</strong> tracking mobile objects with wireless sensor networks, nodes collaboratively<br />

track a mobile target by <strong>for</strong>ming a group of spatially co-located nodes around<br />

the target. If a node initially detects a target, its reaction to this target-detection<br />

event depends on previous events: If the node has previously received a notification<br />

(i.e., an event) that a nearby node is already a member in a tracking group,<br />

then the node joins that group. If the node has not received such a notification,<br />

it <strong>for</strong>ms a new group. In this example, the behavior of a node in reaction to the<br />

target-detection event (i.e., creating a new tracking group or joining an existing<br />

one) depends on both the detection event itself and the node’s current state (i.e.,<br />

whether a group has already been <strong>for</strong>med in the vicinity of the node or not).<br />

Since there is no dedicated abstraction to model program state in the eventdriven<br />

model, programmers typically save the program state in variables. However,<br />

keeping state in variables has two important implications that obscure the<br />

structure of program code, severely hamper its modularity, and lead to issues<br />

with resource efficiency and correctness. Firstly, to share state across multiple<br />

actions, the state-keeping variables must persist over the duration of all those<br />

actions. Automatic (i.e., local) variables of procedural programming languages<br />

do not meet this requirement, since variable lifetime is bound to functions. Thus<br />

the local variable stack is unrolled after the execution of the function that implements<br />

an action. Instead, programmers have the choice to use either global<br />

variables or to manually store a state structure on the heap. The latter approach<br />

is often inapplicable as it requires dynamic memory management, which<br />

is rarely found in memory-constrained embedded systems because it is considered<br />

error-prone and because it incurs significant memory overhead. The <strong>for</strong>mer<br />

and by far more typical approach, however, also incurs significant memory overhead.<br />

Since global variables also have “global” lifetime, this approach permanently<br />

locks up memory—even if the state is used only temporarily, <strong>for</strong> example,<br />

during network initialization. As a result, manual state-keeping makes eventdriven<br />

programs memory inefficient since they cannot easily reuse memory <strong>for</strong><br />

temporary data. This is ironic, since one of the main reasons <strong>for</strong> introducing the


1.3. Thesis <strong>State</strong>ment 3<br />

event-driven model has been that its support in the underlying system software<br />

can be implementred resource efficiently.<br />

Secondly, the association of events to actions is static—there is no explicit support<br />

<strong>for</strong> adopting this association depending on the program state. As a consequence,<br />

in actions, programmers must manually dispatch the control flow to the<br />

appropriate function based on the current state. The additional code <strong>for</strong> state<br />

management and state-based function demultiplexing can obscure the logical<br />

structure of the application and is an additional source of error. Also, it hampers<br />

modularity since even minor changes in the state space of a program may<br />

require modifying multiple actions and may thus affect much of the program’s<br />

code base.<br />

While the identified issues are not particularly troublesome in relatively small<br />

programs, such as application prototypes and test cases, they pose significant<br />

problems in larger programs. But as the field of wireless sensor networks matures,<br />

its applications are getting more complex—projects grow and so does the<br />

code base and the number of programmers involved. Leaving these issues unsolved<br />

may severely hamper the field of wireless sensor networks to mature.<br />

1.3 Thesis <strong>State</strong>ment<br />

Because of the limitations outlined in the previous section, we argue that the<br />

simple event/action-abstraction of the event-driven programming model is inadequate<br />

<strong>for</strong> large or complex sensor network programs. The natural question<br />

then is, can the inadequacies of the event-driven programming model be repaired<br />

without impairing its positive aspects? In this dissertation we answer this<br />

question affirmatively. Concretely, we present the Object <strong>State</strong> <strong>Model</strong> (OSM),<br />

a programming model that extends the event-driven programming paradigm<br />

with an explicit abstraction and notion of hierarchical and concurrent program<br />

states. Our thesis is that such a state-based model allows to specify well-structured,<br />

modular, and memory-efficient sensor-node programs, yet requires as little runtimeresources<br />

as the event-driven model.<br />

1.4 Contributions<br />

The main contribution of this work then is to show<br />

• that OSM provides adequate abstractions <strong>for</strong> the specification of well structured<br />

and highly modular programs,<br />

• how OSM supports memory-efficient programming, and, finally,<br />

• that OSM does not incur significant overhead in the underlying system<br />

software and can thus indeed be implemented on resource-constrained<br />

sensor nodes.<br />

In this dissertation we present four elements to support our claim: (1) OSM,<br />

an abstract, state-based programming model, which is a state-extension to the<br />

conventional event-based programming model, (2) an implementation language


4 Chapter 1. Introduction<br />

<strong>for</strong> sensor nodes that is based on the OSM model, (3) a compiler <strong>for</strong> the proposed<br />

language that generates event-driven program code, and (4) an execution<br />

environment <strong>for</strong> the generated code on BTNODE sensor-node prototypes. The<br />

following paragraphs present the key concepts of our solution, namely using an<br />

explicit notion of state <strong>for</strong> structuring programs, automated state management,<br />

efficient memory management based on the use of state as a lifetime qualifier <strong>for</strong><br />

variables, and the automatic trans<strong>for</strong>mation of OSM programs into conventional<br />

event-driven programs.<br />

1.4.1 Modular and Well-Structured Design<br />

In the state-based model of OSM, the main structuring element of program code<br />

is an explicit notion of program state. For every explicit program state the programmer<br />

can specify any number of actions together with their trigger conditions<br />

(over events). At runtime, actions are only invoked when their associated<br />

program state is assumed and when their trigger condition evaluates to true.<br />

As a consequence, the association of events to actions in OSM depends on the<br />

current state (unlike in the event-driven model), that is, invocations of actions<br />

become a function of both the event and the current program machine state. This<br />

concept greatly enhances modularity, since every state and its associated actions<br />

can be specified separately, independent of other states and their actions. Thus,<br />

modifying the state space of a program requires only local changes to the code<br />

base, namely to the implementations of the affected states. In contrast, in the<br />

event-driven model, such modifications typically affect several actions throughout<br />

much of the entire code.<br />

1.4.2 Automated <strong>State</strong> Management<br />

Generally, OSM moves program-state management to a higher abstraction layer<br />

and makes it an explicit part of the programming model. Introducing state into<br />

the programming model allows to largely automate the management of state<br />

and state transitions—tasks which had to be per<strong>for</strong>med manually be<strong>for</strong>e. The<br />

elimination of manually written code <strong>for</strong> the management of state-keeping variables<br />

and state-based function demultiplexing allows programmers to focus on<br />

the logical structure of the application and removes a typical source of error.<br />

1.4.3 Memory-Efficient <strong>State</strong> Variables<br />

Even in state-based models, variables can be a convenient means <strong>for</strong> keeping<br />

program state, <strong>for</strong> example, to model a very large state space that would be impractical<br />

to specify explicitly or to store state that does not directly affect the<br />

node’s behavior. Instead of having to revert to wasteful global variables (as in<br />

the event-driven model), OSM supports variables that can be attributed to an<br />

explicitly modeled state. This state can then be utilized to provide a novel scope<br />

and lifetime qualifier <strong>for</strong> these variables, which we call state variables. <strong>State</strong> variables<br />

exist and can share in<strong>for</strong>mation—even over multiple action invocations—<br />

when their associated state is assumed at runtime. During compilation, state<br />

variables of mutually exclusive states are automatically mapped to overlapping


1.5. Thesis Organization 5<br />

memory regions. The memory used to store state variables can be managed automatically<br />

by the compiler, very much like automatic variables in procedural<br />

languages. However, this mapping is purely static and thus requires neither a<br />

runtime stack nor dynamic memory management.<br />

<strong>State</strong> variables can reduce or even eliminate the need to use global variables or<br />

manually memory-managed variables <strong>for</strong> modeling temporary program state.<br />

Since state variables provide automatic memory management, they are not only<br />

more convenient to use but also more memory efficient and less error-prone.<br />

Because of state variables OSM programs are generally more memory efficient<br />

compared to event-driven programs.<br />

1.4.4 Light-Weight Execution Environment<br />

The state-based control structure of OSM programs can be automatically compiled<br />

into conventional event-driven programs. Generally, the automatic trans<strong>for</strong>mation,<br />

which is implemented by the OSM compiler, works like manually<br />

coding state in event-driven systems. In the trans<strong>for</strong>mation, however,<br />

the program-state space is automatically reduced and then converted into a<br />

memory-efficient representation as variables of the event program. <strong>State</strong>-specific<br />

actions of OSM are trans<strong>for</strong>med into functions of a host language. Then, code<br />

<strong>for</strong> dispatching the control flow to the generated functions is added to the actions<br />

of the event program. After the trans<strong>for</strong>mation into event-driven code,<br />

OSM programs can run in the same efficient execution environments as conventional<br />

event-driven systems. Thus, the state-based model does not incur more<br />

overhead than the (very efficient) event-driven model and requires only minimal<br />

operating system support.<br />

1.4.5 Evaluation<br />

We use the state-based OSM language to show that a first class abstraction of<br />

state can indeed support well-structured and modular program specifications.<br />

We also show that an explicit abstraction of state can support memory-efficient<br />

programming. To do so, we present an implementation of state variables <strong>for</strong><br />

the OSM compiler. More concretely, we present an algorithm deployed by our<br />

OSM compiler that automatically reclaims unused memory of state variables.<br />

Finally, we show that code generated from OSM specifications can run in the<br />

very light-weight event-driven execution environment on BTnodes.<br />

1.5 Thesis Organization<br />

The remainder of this dissertation is structured as follows. In Chapter 2 we discuss<br />

general aspects of wireless sensor networks. First we characterize wireless<br />

sensor networks and present envisioned as well as existing applications. Then<br />

we discuss state-of-the-art sensor-node hardware, ranging from early research<br />

prototypes to commercialized devices.<br />

In Chapter 3 we present a general background on programming individual<br />

sensor nodes. We start by discussing some general requirements of sensor-node


6 Chapter 1. Introduction<br />

programming. Then we present the three dominant programming models in<br />

use today. We will particularly focus on the runtime support necessitated by<br />

those models and evaluate their suitability with respect to the requirements established<br />

previously. Finally, we present selected state-of-the-art sensor-node<br />

programming frameworks. Chapter 4 is devoted to the detailed examination of<br />

the event-driven programming model. <strong>Based</strong> on our experience with the eventdriven<br />

BTnode system software and other programming frameworks described<br />

in the current literature, we analyze the model’s limitations and drawbacks. We<br />

lay down why the convential event-driven model is not well-suited to describe<br />

sensor-node applications and we stress how a first-class abstraction of program<br />

states could indeed solve some of the most pressing issues.<br />

The next three chapters are devoted to our state-based programming framework<br />

OSM. Chapter 5 presents the OSM model in detail. In particular, the semantics<br />

of the model are discussed. The OSM language and implementation<br />

aspects of the OSM compiler are examined in Chapter 6. We present the OSM<br />

specification language and its mapping to the event-driven execution environment<br />

on BTnodes. The execution environment of OSM programs on BTnodes<br />

is a slightly modified version of our event-driven system software presented in<br />

Sect. 3.5.1. In Chapter 7 we evaluate the practical value of OSM. First we discuss<br />

how OSM meets the requirements <strong>for</strong> programming individual sensor nodes as<br />

set out in previous chapters. Then we discuss how OSM alleviates the identified<br />

limitations of the event-based model. Finally, we show how OSM can be used<br />

to implement concrete applications from the sensor network literature.<br />

In Chapter 8 we present related work. Chapter 9 concludes this dissertation,<br />

discusses the limitations of our work, and ends with directions <strong>for</strong> future work.


2 <strong>Wireless</strong> <strong>Sensor</strong> <strong>Networks</strong><br />

The goal of this dissertation is to develop a software-development framework<br />

appropriate <strong>for</strong> programming wireless sensor network (WSN) applications. To<br />

achieve this goal, we first need to understand what makes one programming<br />

technique appropriate and another one inadequate. Software, as well as the<br />

techniques <strong>for</strong> programming this software, are always shaped by its applications<br />

and by the computing hardware. So in order to develop an understanding<br />

<strong>for</strong> the characteristics, requirements, and constraints of WSNs, we will present<br />

and analyze a range of current and envisioned WSN applications and their computational<br />

plat<strong>for</strong>ms, the sensor nodes.<br />

WSNs have a wide range of applications. The exact characteristics of a particular<br />

sensor network depend largely on its application. However, some characteristics<br />

are shared by most, if not all WSNs. The most important characteristics<br />

are: (1) WSNs monitor physical phenomena by sampling sensors of individual<br />

nodes. (2) They are deployed close to the phenomenon and (3), once deployed,<br />

operate without human intervention. And finally (4), the individual<br />

sensor nodes communicate wirelessly. These general characteristics together<br />

with the application-specific characteristics translate more or less directly into<br />

technical requirements of the employed hardware and software and thus shape<br />

(or at least should shape) their design. Indeed, the technical requirements of<br />

many of the more visionary WSN applications go far beyond what is technically<br />

feasible today. To advance the field of WSNs, innovative hardware and software<br />

solutions need to be found.<br />

Regarding WSN software, classical application architectures and algorithm<br />

designs need to be adapted to the requirements of WSNs. However, this is not<br />

enough. Today’s programming models and operating systems also do not match<br />

WSN requirements and are thus equally ill-suited to support such new software<br />

designs. <strong>Programming</strong> models must be revised in order to facilitate the design<br />

of WSN applications with expressive abstractions. And operating systems need<br />

to be adapted to support these new programming models on innovative sensornode<br />

hardware designs.<br />

Be<strong>for</strong>e looking at the software development tools and process in the next chapter,<br />

we present the characteristics of WSN applications and their computational<br />

plat<strong>for</strong>ms in this chapter. We start by presenting a number of application scenarios<br />

in Sect. 2.1. From these applications we derive common application characteristics<br />

in Sect. 2.2. Some of the software requirements can be derived directly<br />

from those characteristics, while others are imposed by the characteristics of sensor<br />

nodes. There<strong>for</strong>e we present classes of sensor-node designs, their hardware<br />

components, and selected sensor-node hardware plat<strong>for</strong>ms in Sect. 2.3. We explain<br />

how these hardware designs try to meet the WSN requirements and which<br />

constraints they impose on the software themselves. Be<strong>for</strong>e summarizing this<br />

chapter in Sect. 2.5 we briefly relate wireless sensor nodes to embedded systems


8 Chapter 2. <strong>Wireless</strong> <strong>Sensor</strong> <strong>Networks</strong><br />

in Sect. 2.4.<br />

2.1 WSN Applications<br />

WSN share a few common characteristics, which make them recognizable as<br />

such. However, sensor networks have such a broad range of existing and potential<br />

applications, that these few common characteristics are not sufficient to<br />

describe the field comprehensively. In this section we present concrete WSN<br />

applications to exemplify their diversity. <strong>Based</strong> on this presentation we derive<br />

WSN characteristics in the next section. Similar surveys on the characteristics of<br />

WSNs can be found in [70, 101].<br />

2.1.1 Environmental Observation<br />

One application domain of WSN is the observation of physical phenomena and<br />

processes in a confined geographical region, which is covered by the WSN. Several<br />

WSNs have already been deployed in this domain. For example, a WSN has<br />

been used to monitor the behavior of rocks beneath a glacier in Norway [88]. The<br />

goal of the deployment is to gain an understanding <strong>for</strong> the sub-glacier dynamics<br />

and to estimate glacier motion. <strong>Sensor</strong> nodes are placed in drill holes in and<br />

beneath the glacier. The holes are cut with a high-pressure hot water drill. The<br />

sensor nodes are not recoverable; their width is constrained by the size of the<br />

drill hole, which is 68 mm. Communication between the nodes in, and the base<br />

station on top of the glacier follows a pre-determined schedule. To penetrate<br />

the up to 100 meters of ice, the nodes boost the radio signal with an integrated<br />

100 mW power amplifier. The network is expected to operate <strong>for</strong> at least one<br />

year.<br />

To monitor the impact of wind farms on the sedimentation process on the<br />

ocean floor and its influence on tidal activity [87] a WSN is being used off the<br />

coast of England. The sensor nodes are dropped from ships at selected positions<br />

and are held in position by an anchor. Each of the submerged sensors is connected<br />

to a floating buoy, which contains the radio transceiver and GPS receiver.<br />

The nodes measure pressure, temperature, conductivity, current, and turbidity.<br />

Another maritime WSN deployment called ARGO is used to observe the upper<br />

ocean [63]. ARGO provides a quantitative description of the changing state<br />

of the upper ocean and the patterns of ocean climate variability over long time<br />

periods, including heat and freshwater storage and transport. ARGO uses freedrifting<br />

nodes that operate at depths up to 2000 m below sea-level but surface<br />

every 10 days to communicate their measurements via satellite. Each node is<br />

about 1.3 m long, about 20 cm in diameter, and weigh about 40 kg. Much of the<br />

node’s volume is taken up by the pressure case and the mechanism <strong>for</strong> submerging<br />

and surfacing. The per-node cost is about $ 15.000 US; it’s yearly operation<br />

(i.e., deployment, data analysis, project management, etc.) accounts <strong>for</strong> about<br />

the same amount. The nodes are designed <strong>for</strong> about 140 submerge / resurface<br />

cycles and are expected to last almost 4 years. After that time most nodes will<br />

sink to the ocean floor and are thus not recoverable. Currently almost 3000 nodes<br />

are deployed.


2.1. WSN Applications 9<br />

WSNs are also used in precision agriculture to monitor the environmental factors<br />

that influence plant growth, such as temperature, humidity light, and soil<br />

moisture. A WSN has been deployed in a vineyard [15] to allow precise irrigation,<br />

fertilization, and pest control of individual plants. Other examples <strong>for</strong> environmental<br />

observations are the detection and tracking of wildfires, oil spills,<br />

and pollutants in the ground, in water, and in the air.<br />

2.1.2 Wildlife and Farm-Animal Monitoring<br />

Monitoring the behavior and health of animals is another application domain of<br />

WSNs. Some wild animals are easily disturbed by human observers, such as the<br />

Leach’s Storm Petrel. To unobtrusively monitor the breeding behavior of these<br />

birds in their natural habitat, a WSN has been deployed on Great Duck Island,<br />

Maine, USA [85, 112]. Be<strong>for</strong>e the start of the breeding season, sensor nodes<br />

are placed in the birds’ nesting burrows and on the ground. They monitor the<br />

burrows’ micro climate and occupancy state. About one hundred nodes have<br />

been deployed. They are expected to last <strong>for</strong> an entire breeding period (about<br />

seven months), after which they can be recollected. Another WSN described in<br />

[117, 118] aims to recognize a specific type of animal based on its call and then<br />

to locate the calling animal in real-time. The heterogeneous, two-tired sensor<br />

network is composed of so-called micro nodes and macro nodes. The smaller,<br />

less expensive, but also less capable micro nodes are deployed in great numbers<br />

to exploit spatial diversity. The fewer but more powerful macro nodes combine<br />

and process the micro node sensing data. The target recognition and location<br />

task is divided into two steps. All nodes first independently determine whether<br />

their acoustic signals are of the specified type of animal. Then, macro nodes<br />

fuse all individual decisions into a more reliable system-level decision using<br />

distributed detection algorithms. In a similar approach, [64, 105] investigates<br />

monitoring of amphibian populations in the monsoonal woodlands of northern<br />

Australia.<br />

WSNs have also been used or are planned to be used to monitor and track<br />

wild species and farm animals, such as cows, deer, zebras, fishes, sharks, or<br />

whales. In these scenarios, sensor nodes are attached to individual animals in<br />

order to determine their health, location, and interaction patterns. In these applications,<br />

WSNs are superior to human observers because often humans cannot<br />

easily follow the targeted animals and because human observation is too cost intensive.<br />

The ZebraNet [67, 81] WSN is used to observe wild zebras in a spacious<br />

habitat at the Mpala Research Center in Kenya. A main research goal is to understand<br />

the impact of human development on the species. The WSN monitors<br />

the behavior of individual animals (e.g., their activity and movement patterns)<br />

and interactions within a species. The nodes, which are worn as a collar, log<br />

the sensory data and exchange them with other nodes, as soon as they get into<br />

communication range. The data is then retrieved by researchers who regularly<br />

pass through the observation area with a mobile base stations mounted on a car<br />

or plane.<br />

A restoration project after an oil spill in the Gulf of Alaska identified critical<br />

habitats of the Pacific halibut by determining its life-history patterns [102]. The<br />

knowledge of the fishes’ critical habitat aids fisheries managers in making de-


10 Chapter 2. <strong>Wireless</strong> <strong>Sensor</strong> <strong>Networks</strong><br />

cisions regarding commercial and sport fishing regulations. The sensor nodes<br />

are attached to a number of individual fishes in the <strong>for</strong>m of so-called pop-up<br />

tags. The tags collect relevant sensory data and store them. After the observation<br />

period a mechanism releases the tags, which then “pop up” to the surface<br />

and communicate their data by satellite. Typically tags are lost unless washed<br />

ashore and returned by an incidental finder.<br />

WSNs are also used in agriculture, <strong>for</strong> example in livestock breeding and cattle<br />

herding. The Hogthrob project [24], <strong>for</strong> example, aims at detecting the heat<br />

periods of sows in order to determine the right moment of artificial fertilization.<br />

The intended area of operation are farms with a high volume of animals (2000<br />

individuals and more). To be economically sensible, sensor nodes must last <strong>for</strong><br />

2 years at a cost of about one Euro.<br />

To reduce the overhead of installing fences and improving the usage of feeding<br />

lots in cattle herding, a WSN implements virtual fences [28]. An acoustic<br />

stimulus scares the cows away from the virtual fence lines. The movement pattern<br />

of the animals in the herd is analyzed and used to control the virtual fences.<br />

The sensor nodes are embedded into a collar worn by the cow.<br />

2.1.3 Intelligent Environments<br />

Applications <strong>for</strong> WSN cannot only be found in the outdoors. Instead of attaching<br />

sensor nodes to animals, they may also be embedded within (or attached<br />

to) objects of the daily life. Together with a typically fixed infrastructure within<br />

buildings, such WSNs create so-called intelligent environments. In such environments,<br />

augmented everyday objects monitor their usage pattern and support<br />

their users in their task through the background infrastructure.<br />

The Mediacup [16], a coffee cup augmented with a sensor node, detects the<br />

cup’s current usage pattern, <strong>for</strong> example, if it is sitting in the cupboard, filled<br />

with hot coffee, drunken from, carried around, or played with. The cup knows<br />

when the coffee in the cup is too hot to drink and warns the user. A network of<br />

Mediacups can detect whether there is a meeting taking place and automatically<br />

adjust the label of an electronic doorplate. Similarly, to support the assembly<br />

of complex kits of all kinds, <strong>for</strong> example, machinery, the individual parts and<br />

tools could be augmented with sensor nodes. In a prototypical project described<br />

in [12], a WSN assists in assembling do-it-yourself furniture to save the users<br />

from having to study complex assembly instructions.<br />

2.1.4 Facility Management<br />

To get indications <strong>for</strong> potential power savings, a WSN monitors the power consumption<br />

in office buildings and in private households [69]. Data is measured<br />

by the power-adapter like sensors that are located either in electrical outlets or<br />

at sub-distribution nodes, such as fuseboxes. Measurements are collected by a<br />

central server and can be accessed and interpreted in real-time. <strong>Sensor</strong> nodes<br />

are powered through the power grid in the order of tens to hundreds. Their lifetime<br />

should be equivalent to the lifetime of the building. Obviously, their combined<br />

power consumption should be very low, that is, well below the savings<br />

gained through its employment. Another possible application in the living and


2.1. WSN Applications 11<br />

working space are micro-climate monitoring to implement fine-grained control<br />

of Humidity, Ventilation and Air Conditioning (HVAC).<br />

2.1.5 Logistics and Asset Tracking<br />

Another large application domain is tracking of products and assets, such as<br />

cargo containers, rail cars, and trailers. Again, sensor nodes in this domain are<br />

attached to the items to be monitored and typically log the items’ position and<br />

state and notify users of critical conditions.<br />

For example, the AXtracker [82] is a commercial wireless sensor node <strong>for</strong> asset<br />

tracking and fleet management which can report conditions such as a door<br />

being open, a decrease or increase in temperature or moisture, and it can detect<br />

smoke. SECURIfood [96] is a commercial WSN <strong>for</strong> cold-chain management. The<br />

networks detects inadequate storage conditions of frozen or refrigerated products<br />

early in order to issue warnings. In a research project [106] a WSN has been<br />

used to augment products as small as an egg carton in a supermarket in order to<br />

facilitate the early removal of damaged or spoiled food items. If the product is<br />

damaged (e.g., because it has been dropped) or has passed its best-be<strong>for</strong>e date, it<br />

connects to the PDA or mobile phone of an employee on the sales floor to report<br />

its condition. Such a solution is superior to RFID or manual barcode scanning,<br />

because sensor nodes can take the individual product and storage history into<br />

account. Obviously, sensor nodes need to be very inexpensive, that is, in the<br />

range of noticeably less than a few percent of the tagged product, which is the<br />

profit margin <strong>for</strong> food items. The node’s life must span the period from production<br />

to sale. After consumption of the product the sensor node is disposed with<br />

its packaging.<br />

2.1.6 Military<br />

Many of the early, mostly US-based WSN research projects were in the military<br />

domain. For example, WSNs are used <strong>for</strong> tracking enemy vehicles [6]. <strong>Sensor</strong><br />

nodes could be deployed from unmanned aerial vehicles in order to detect<br />

the proximity of enemy tanks with magnetometers. In such an application sensor<br />

nodes need to be unnoticeably small in order to prevent their detection and<br />

removal or the nodes must be too numerous to be removed effectively. In either<br />

case, the nodes are lost after deployment. A WSN integrated into anti-tank<br />

mines ensures that a particular area is covered with mines [90]. The mines estimate<br />

their orientation and position. Should they detect a breach in the coverage,<br />

a single mine is selected to fill in the gap. The mine can hop into its new position<br />

by firing one of its eight integrated rocket thrusters. Obviously, sensor<br />

nodes on mines are disposable. WSNs can also be used in urban areas to locate<br />

a shooter. A system has been implemented that locates the shooter by detecting<br />

and analyzing the the muzzle blast and shock wave using acoustic sensors [109].


12 Chapter 2. <strong>Wireless</strong> <strong>Sensor</strong> <strong>Networks</strong><br />

2.2 WSN Characteristics<br />

2.2.1 Deployment and Environmental Integration<br />

A common characteristic of wireless sensor networks is their deployment in the<br />

physical environment. In order to monitor real-world phenomena in situ, sensor<br />

nodes are deployed locally, close to where the phenomena is expected. The<br />

sensory range and accuracy of individual nodes is typically limited. If applications<br />

require more accurate data (e.g., in the shooter localization application) or<br />

a complete coverage of an extensive area (e.g., vehicle tracking), nodes must be<br />

deployed in larger numbers. Current deployments typically consist of a few to<br />

several tenths of sensor nodes, though deployments of several thousand devices<br />

are envisioned by researchers.<br />

The way sensor nodes are deployed in the environment differs significantly<br />

depending on the application. Nodes may be carefully placed and arranged at<br />

selected locations as to yield best sensing results. Or they may be deployed by<br />

air-drop resulting in a locally concentrated but random arrangement. <strong>Sensor</strong><br />

nodes may also be attached directly to possibly mobile objects that are to be<br />

monitored, <strong>for</strong> example, to animals, vehicles, or containers.<br />

For outdoor applications, sensor nodes need to be sealed from environmental<br />

influences (such as moisture, fungus, dust, and corrosion) to protect the sensitive<br />

electronics. Sealing can be done with con<strong>for</strong>mal coating or packaging. Both<br />

strategies may have influences on the quality of sensor readings and wireless<br />

communications. Packaging may also contribute significantly to the weight and<br />

size of sensor nodes.<br />

2.2.2 Size, Weight, and Cost<br />

The allowable size, weight, and cost of a sensor node largely depends on application<br />

requirements, <strong>for</strong> example, the intended size of deployment. Particularly<br />

in large scale, disposable (i.e., non recoverable) sensor node deployments, individual<br />

nodes must be as cheap as possible. In the military domain, <strong>for</strong> example,<br />

Smart Dust sensor nodes [120] aim at a very large scale deployment of nodes<br />

the size of one cubic millimeter and very low cost. For commercial applications<br />

(such as logistics, product monitoring, agriculture and animal farming, facility<br />

management, etc.), cost will always be a prime issue. On the other extreme,<br />

nodes can be as big as suitcases and cost up to several hundred Euros, <strong>for</strong> example,<br />

<strong>for</strong> weather and ocean monitoring. Application scenarios in which the<br />

sensor network is to float in the air pose stringent weight restrictions on nodes.<br />

We expect that future sensor-node technology will typically meet the weight<br />

and size requirements, but that stringent requirements <strong>for</strong> low cost will significantly<br />

limit the nodes’ energy budget and available resources (see below).<br />

2.2.3 Limited Energy Budget<br />

<strong>Wireless</strong> sensor nodes typically have to rely on the finite energy reserves from a<br />

battery. Remote, untethered, unattended, and unobtrusive operation of sensor<br />

networks as well as the sheer number of nodes in a network typically precludes


2.2. WSN Characteristics 13<br />

the replacement of depleted batteries. Harvesting energy from the environment<br />

is a promising approach to power sensor nodes currently under research. First<br />

results suggest that under optimal environmental conditions, energy harvesting<br />

can significantly contribute to a node’s energy budget, but may not suffice as<br />

sole energy resource [99].<br />

Each WSN application has specific constraints regarding size, weight, and cost<br />

of individual sensor nodes. These factors directly constrain what can be reasonably<br />

integrated into sensor nodes. Particularly, power supplies contribute significantly<br />

to the size, weight, and cost of sensor nodes. In the majority of WSN<br />

applications, energy is a highly limited resource.<br />

2.2.4 Lifetime<br />

The application requirements regarding sensor network lifetime can vary<br />

greatly from a few hours (e.g., furniture assembly) to many years (e.g., ocean<br />

monitoring). But also the notion of WSN lifetime can vary from application to<br />

application. While some WSN applications can tolerate a large number of node<br />

failures and still produce acceptable results with only a few nodes running, other<br />

applications may require dense arrangements. But the network lifetime will always<br />

depend to a large degree on the lifetime of individual sensor nodes.<br />

<strong>Sensor</strong>-node lifetime mainly depends on the node’s power supply and its<br />

power consumption. But as the available energy budget is often tightly constrained<br />

by application requirements on the node’s size, wight and cost and,<br />

at the same time, lifetime requirements are high, low-power hardware designs<br />

and energy aware software algorithms are imperative. Today, sensor network<br />

lifetime is one of the major challenges of WSN research.<br />

2.2.5 Limited Computing Resources<br />

<strong>Sensor</strong> nodes designs often trade off computing resources <strong>for</strong> one of the fundamental<br />

characteristics of sensor nodes, size, monetary cost, and lifetime (i.e.,<br />

energy consumption). Computing resources, such as sensors, wireless communication<br />

subsystems, processors, and memories, significantly contribute to all<br />

three of them. <strong>Sensor</strong>-node designs often try to strike a balance between sensor<br />

resolution, wireless communication bandwidth and range, CPU speeds, and<br />

memory sizes on the one hand, and cost, size, and power consumption on the<br />

other hand.<br />

For many WSN applications, cost is a crucial factor. Assuming mass production,<br />

the price of integrated electronics and thus the price per sensor node is<br />

mainly a function of the die size, that is, the number of transistors integrated.<br />

The size of the node on the die determines the how many devices can be produced<br />

from a single wafer while wafers induce about constant costs regardless<br />

of what is produced from them. That is the main reason why complex 32-bit<br />

microcontroller architectures are much more expensive then comparatively simple<br />

8-bit architectures. As we will detail in the next section, where we will look<br />

at selected sensor-node components, an increase of the address-bus size from 8<br />

to 32-bit of typical sensor-node processors today leads to a ten-fold increase in<br />

processor price. Moore’s law (see [91]) predicts that more and more transistors


14 Chapter 2. <strong>Wireless</strong> <strong>Sensor</strong> <strong>Networks</strong><br />

can be integrated, and thus integrated electronics get smaller and less expensive<br />

in the future. While this means that sensor-network applications with more and<br />

more (possibly disposable) nodes can be deployed, it does not necessarily imply<br />

that the resources of individual nodes will be less constrained. Memories in<br />

general, and SRAM in particular, require high transistor densities, and thus are<br />

also a significant cost factor.<br />

Also, the energy consumption of sensor nodes mainly depends on the deployed<br />

hardware resources (and their duty cycle). More powerful devices quite<br />

literally consume more power. Most of the power consumption of integrated devices<br />

stems from switching transistors (a CPU cycle, refreshing memories, etc).<br />

Again, less powerful processor architectures with less gates have an advantage.<br />

As a consequence, computing resources are often severely constrained, even<br />

when compared to traditional embedded systems, which have similar cost constraints<br />

but are typically mains powered. We expect that these limitations of<br />

sensor networks will not generally change in the future. Though we can expect<br />

advances in chip technology and low-power design, which will lift the most<br />

stringent restrictions of current applications. But the attained reductions in pernode<br />

price, size, and energy consumption also open up new applications with<br />

yet tighter requirements. Less powerful devices will remain to have the edge in<br />

terms of price and power consumption.<br />

2.2.6 Collaboration<br />

The collective behavior of a wireless sensor network as a whole emerges from<br />

the cooperation and collaboration of the many individual nodes. Collaboration<br />

in the network can overcome and compensate <strong>for</strong> the limitations of individual<br />

nodes. The wireless sensor network as a whole is able to per<strong>for</strong>m tasks that<br />

individual nodes cannot and that would be difficult to realize with traditional<br />

sensing systems, such as satellite systems or wired infrastructures.<br />

2.2.7 Back-End Connectivity<br />

To make the monitoring results of WSN available to human observers or control<br />

applications, they may be connected to a fixed communication infrastructure,<br />

such as <strong>Wireless</strong> LAN, satellite networks, or GSM networks. The WSN may<br />

be connected to this communication infrastructure via one or several gateways.<br />

Connectivity to a communication infrastructure requires that either nodes from<br />

the WSN operate as gateways, or that fixed gateways are installed. Such gateway<br />

typically posses two network interfaces. Acting as a gateway between the<br />

WSN and the communication infrastructure (which is typically not optimized<br />

<strong>for</strong> low power consumption) places additional burdens on sensor nodes. On the<br />

other hand, installing fixed gateways is often too expensive.<br />

Rather than relaying data from the WSN into a fixed infrastructure, monitoring<br />

results may also be retrieved by mobile terminals. In ZebraNet, <strong>for</strong> example,<br />

sensing results are collected by humans with laptop computers [67]. Since<br />

permanent connectivity from the WSN to the mobile terminals cannot be guaranteed,<br />

sensor nodes are required to temporarily store their observations.


2.2. WSN Characteristics 15<br />

2.2.8 Mobility and Network Dynamics<br />

<strong>Sensor</strong> nodes may change their location after deployment. They may have automotive<br />

capabilities, as described in the military application above or they may<br />

be attached to mobile entities, like animals. Finally, nodes may be carried away<br />

by air and water currents.<br />

If nodes are mobile, the communication topology may change as nodes move<br />

out of communication range of their <strong>for</strong>mer communication partners. However,<br />

the network topology may change even if nodes are stationary, because nodes<br />

fail or more nodes are deployed. Additionally, communication links may be<br />

temporarily disrupted as mobile objects, such as animals or vehicles, or weather<br />

phenomena obstruct RF propagation. Finally, some nodes may choose to turn<br />

their radio off in order to save power. If the sensor network is sparsely connected,<br />

individual node or link failures may also result in network partitions.<br />

As a consequence of their integration into the environment sensor nodes may<br />

be destroyed by environmental influences, <strong>for</strong> example, corroded by sea water<br />

or crushed by a glacier. If batteries cannot be replaced, they may run out of<br />

power. Increasing the node population (and hence the quality and quantity or<br />

data readings) may be desirable at places that have been found to be interesting<br />

after the initial deployment. After an initial deployment, a (possibly repeated)<br />

re-deployment of sensor nodes may become necessary in order to replace failed<br />

nodes or to increase the node population at a specific location. All these factors<br />

may lead to a dynamic network, with changing topologies, intermittent partitions,<br />

and variable quality of service.<br />

2.2.9 Bursty Traffic<br />

<strong>Wireless</strong> sensor networks may alternate between phases of low-datarate traffic<br />

and phases of very bursty, high-datarate traffic. This may be the case, <strong>for</strong> example,<br />

if the wireless sensor network is tasked to monitor the occurrence of certain<br />

phenomena (as opposed to continuous sampling), and that phenomenon is detected<br />

by a number of nodes simultaneously.<br />

2.2.10 Dynamic Role Assignment<br />

To optimize certain features of the sensor network, such as network per<strong>for</strong>mance,<br />

lifetime, and sensing quality, the nodes of a sensor network may be<br />

dynamically configured to per<strong>for</strong>m specific functions in the network. This socalled<br />

role assignment [42, 98] is based on static as well as dynamic parameters of<br />

the nodes, such as hardware configuration, network neighbors, physical location<br />

within the network (e.g., edge node or distance to the infrastructure gateway),<br />

or remaining battery levels. For example, in dense networks, where all areas of<br />

interest are covered by multiple nodes (both in terms of network and sensor coverage),<br />

some of the nodes may be switched off temporarily to conserve energy.<br />

These nodes can then later replace the nodes that have run out of battery power<br />

in the meantime, thus increasing the overall lifetime of the sensor network [122].<br />

As it is often difficult to anticipate the node parameters be<strong>for</strong>e deployment, role<br />

assignment is typically per<strong>for</strong>med in situ by the sensor network itself. Typically,


16 Chapter 2. <strong>Wireless</strong> <strong>Sensor</strong> <strong>Networks</strong><br />

it is first per<strong>for</strong>med right after deployment, in the network initialization phase.<br />

Then, changing parameters of a node and its environment may regularly prompt<br />

reassignment of the node’s role.<br />

2.2.11 Node Heterogeneity<br />

While many sensor networks consist of identical nodes, <strong>for</strong> some applications it<br />

can be advantageous to deploy multiple hardware configurations. For example,<br />

nodes may be equipped with different sensors; some nodes may be equipped<br />

with actuators, while others may be equipped with more computing power<br />

and memory. Nodes with a powerful hardware configuration typically consume<br />

more energy, but in turn can run more complex software and per<strong>for</strong>m<br />

special functions in the network, such as communication backbone, gateway to<br />

the background infrastructure, or host <strong>for</strong> sophisticated computations. Low capability<br />

nodes typically per<strong>for</strong>m simpler tasks but have higher lifetimes at lower<br />

cost. These setups can lead to clustered and multi-tiered architectures, where the<br />

cluster-heads are equipped with more computing and memory resources.<br />

2.3 <strong>Sensor</strong> Nodes<br />

As we have seen in the previous sections, different applications have different<br />

requirements regarding lifetime, size, cost, etc. While functional prototypes<br />

have been realized <strong>for</strong> most the applications described above, many of the employed<br />

sensor-nodes do not meet all their requirements. Concretely, most devices<br />

are either too big, too expensive <strong>for</strong> the application at hand, or they fall<br />

short of lifetime requirements. There<strong>for</strong>e miniaturization, low-power and lowcost<br />

designs are possibly the most pressing technical issues <strong>for</strong> sensor nodes.<br />

Most sensor nodes have been built as research prototypes or proof-of-concept<br />

implementations used to investigate certain aspects of future sensor nodes, networks,<br />

algorithms, and applications. Indeed, even the designs which have been<br />

commercialized are targeted at research institutions and very early adopters.<br />

2.3.1 Device Classes<br />

In today’s WSN deployments and research installations we see three classes of<br />

sensor nodes of different degrees of maturity. The first class are commodity devices<br />

that are being used as sensor nodes, though they are not actually built <strong>for</strong><br />

that purpose. Devices in that class are laptop computers, PDAs, mobile phones,<br />

cameras, etc. Commodity devices are often used <strong>for</strong> rapid application prototyping,<br />

where the actual device characteristics are less relevant. The second class<br />

of sensor nodes are custom built from commercially-available electronics components.<br />

These nodes are closer to the actual requirements of WSN and often<br />

per<strong>for</strong>m better than commodity devices. However, they still do not meet most<br />

of the envisioned characteristics, such as “mote” size, years of lifetime, and a<br />

price in the order of cents. Typically these nodes target at a broad applicability.<br />

Some nodes designs are available commercially, such as the latest version<br />

of the BTnode. Because of their availability and relatively low cost, these nodes


2.3. <strong>Sensor</strong> Nodes 17<br />

dominate current WSN deployments. The last class of sensor nodes, finally, are<br />

highly integrated designs that combine all the functional parts of a sensor node<br />

(CPU, memories, wireless communication, etc.) into a single chip or package.<br />

These devices are the best “per<strong>for</strong>mers” but still remain to be research prototypes<br />

produced in very few numbers only. Below we will discuss these classes<br />

of sensor node in more detail.<br />

Commodity Devices<br />

Rather than building custom sensor nodes, some researchers have utilized commercially<br />

available commodity devices to build prototypical sensor network<br />

algorithms and applications. Commodity devices, such as laptop computers,<br />

PDAs, mobile phones, cameras, etc., can be used to per<strong>for</strong>m special functions in<br />

the network. In some research projects they even have been operated as sensor<br />

node replacements.<br />

There are a number of benefits to this approach: commodity devices are readily<br />

available and provide a variety of sensors and resources. Many commodity<br />

devices provide standardized wired and wireless interfaces (such as RS-232<br />

serial ports, USB, Bluetooth, and Infrared) and application protocols (e.g, RF-<br />

COM and OBEX), which allow to use the device’s functionality without a major<br />

programming ef<strong>for</strong>t. For laptop computers, <strong>for</strong> example, there is an immense<br />

variety of peripherals available. Custom-made peripherals can be connected<br />

to the computer via its general-purpose IO interface. When programming is<br />

required, software development on commodity devices is typically more convenient<br />

compared to custom-made sensor nodes because these devices offer established<br />

software-development environments and operating-system support.<br />

Furthermore, no knowledge of embedded-system design and chip design is<br />

required, thereby opening up the possibility of work in WSNS even <strong>for</strong> nonexperts<br />

in embedded-systems design.<br />

There are several examples where commodity devices are used in WSN. In<br />

[117, 118], <strong>for</strong> example, PDAs and embedded PCs, respectively, are used as cluster<br />

heads. For wirelessly interfacing the sensor nodes, the cluster heads are<br />

equipped with proprietary hardware extensions. In [116], PDAs are used as<br />

wireless sensor nodes in a setup <strong>for</strong> the analysis of beam<strong>for</strong>ming algorithms.<br />

Further examples <strong>for</strong> the use of commodity devices in WSNs can be found<br />

in [21, 107].<br />

However, there are a number of drawbacks, which preclude the utilization of<br />

commodity devices in wireless sensor networks. Apart from few niche applications,<br />

commodity devices do not meet WSN requirements, particularly lowcost,<br />

long lifetime, and unattended operation. The computational characteristics<br />

(like memory and CPU) of commodity devices are typically well beyond the<br />

constraints set by WSN applications. Prototypes implemented with commodity<br />

devices typically leave the question unanswered whether the same implementation<br />

could indeed be realized given more realistic cost, size, and lifetime<br />

constraints. Also, commodity devices are often too fragile <strong>for</strong> prolonged use<br />

in the physical environment and typically require extensive configuration and<br />

maintenance.


18 Chapter 2. <strong>Wireless</strong> <strong>Sensor</strong> <strong>Networks</strong><br />

COTS <strong>Sensor</strong> Nodes<br />

A large class of the custom-built sensor nodes are what is referred to as COTS<br />

sensor nodes. COTS nodes are manufactured from several commercially of-theshelf<br />

(COTS) electronic components. The aim of some COTS node developments<br />

is the provision of a versatile, broadly applicable sensor node (e.g., [21, 31, 58]),<br />

while other developments are custom-built with a particular application in mind<br />

(e.g., [61, 67]. Some of the general-purpose designs are commercially available or<br />

are made available <strong>for</strong> academic research at cost price. A typical setup consists<br />

of an RF transceiver and antenna, one or more sensors (or interfaces <strong>for</strong> connecting<br />

external sensor boards), as well as a battery and power regulating circuitry<br />

grouped around a general-purpose processor. While these processors—often<br />

8-bit microcontrollers—typically feature some internal memory, many COTS<br />

sensor-node designs incorporate some additional external memory. The electronic<br />

components are mounted on a printed circuit board (PCB). Some COTS<br />

sensor nodes are composed of multiple PCBs, <strong>for</strong> examples, a main board and<br />

separate daughter boards <strong>for</strong> sensing and RF communication.<br />

The computational resources provided by of most COTS-node designs are<br />

typically within reasonable limits <strong>for</strong> a number of WSN applications. Currently,<br />

the per-node cost is about 80–180 Euro (see, <strong>for</strong> example, [14, 34, 123]). Nodes<br />

built from SMD components can be as small as a one Euro coin, such as the<br />

MICA2DOT node [34]. However antennas, batteries, battery housings, the general<br />

wiring overhead of multiple components, and packaging (if required), preclude<br />

smaller sizes and <strong>for</strong>m factors. The lifetime of COTS nodes ranges from<br />

days to years, depending on the dutycycle.<br />

For many applications COTS nodes may still be too expensive to be deployed<br />

at the large scale, may be too power consuming to achieve the required network<br />

lifetime, or may be too big <strong>for</strong> unobtrusive operation. However, because of their<br />

relatively low price and availability, COTS sensor nodes allow to quickly realize<br />

prototypes, even at a larger scale. Representatives of COTS sensor nodes are the<br />

entire Berkeley Mote family (including the Rene, Mica2, MicaDot, and Mica-Z<br />

nodes [34, 57]), the iMote [31], the three generations of BTnodes [22, 73, 123],<br />

Smart-Its [124], Particle nodes [35], and many others. A survey of sensor nodes<br />

can be found in [14]. Later in this chapter we present some COTS sensor-node<br />

designs in greater detail, such as the BTnode, which has been co-developed by<br />

the author.<br />

<strong>Sensor</strong>-Node Systems-on-a-Chip<br />

Research groups have recently pursued the development of entire sensor-node<br />

systems-on-a-chip (SOC). Such designs integrate most (if not all) sensor-node<br />

subsystems on a single die or multiple dies in one package. This includes microcontrollers<br />

and memories but also novel sensor designs as well as wireless<br />

receivers and transmitters. Also, chip designs have been theorized that include<br />

their own power supply, such as micro fuel cells or batteries deposited onto the<br />

actual chip. A summary of current and potential power sources and technologies<br />

<strong>for</strong> sensor nodes can be found in [99].<br />

With their high integration levels, SOC designs usually consume less power,<br />

cost less, and are more reliable compared to multi-chip systems. With fewer


2.3. <strong>Sensor</strong> Nodes 19<br />

chips in the system, assembly cost as well as size are cut with the reduction in<br />

wiring overhead.<br />

<strong>Sensor</strong>-node SOC developments are still in their early stages and have yet<br />

to be tested in field experiments. Only very few prototypes exist today. The<br />

integration level of sensor-nodes SOCs promise very-low power consumption,<br />

and very small size. The single chip design could also facilitate the easy and<br />

effective packaging. Examples of sensor-node SOCs are Smart Dust [68, 119,<br />

120], the Spec Mote [60], and SNAP [39, 66].<br />

2.3.2 <strong>Sensor</strong>-Node Components<br />

Processors<br />

<strong>Sensor</strong> node designs (both, COTS and SOC) typically feature a reprogrammable<br />

low-power 8-bit RISC microcontroller as their main processor. Operating at<br />

speeds of a few MIPS, it typically controls the sensors and actuators, monitors<br />

system resources, such as the remaining battery power as well as running a custom<br />

application. The microcontroller may also have to control a simplistic RF<br />

radio, however, more sophisticated radio transceivers include their own embedded<br />

processor <strong>for</strong> signal processing. Some applications depend on near real-time<br />

signal processing or complex cryptographic operations. Since the computational<br />

power of 8-bit microcontrollers is often too limited to per<strong>for</strong>m such tasks, some<br />

sensor nodes designs use 16 or even 32-bit microcontroller, or they include additional<br />

ASICs, DSPs, or FPGAs.<br />

However, more processing power only comes at a significant monetary price.<br />

While today 8-bit microcontrollers are about 50 US cents in very high volume<br />

(millions of parts), low-end 32-bit microcontrollers are already $ 5-7 US [103].<br />

Pricing is particularly important <strong>for</strong> applications where the nodes are disposable<br />

and/or are deployed in high volumes. Though is can be safely expected<br />

that prices will decrease further driven by Moore’s Law (to as low as $ 1 US<br />

<strong>for</strong> low-end 32-bit microcontrollers [33] in 2007), less powerful processor architectures<br />

will always be cheaper because they require to integrate less gates (i.e.,<br />

transistors).<br />

Memories<br />

<strong>Sensor</strong> nodes are typically based on microcontrollers, which typically have a<br />

Harvard architecture, that is, they have separate memories <strong>for</strong> data and instructions.<br />

Most modern microcontroller designs feature integrated data and instruction<br />

memories, but do not have a memory management unit (MMU) and thus<br />

cannot en<strong>for</strong>ce memory protection. Some sensor-node designs add external data<br />

memory or non-volatile memory, such as FLASH-ROM.<br />

Microcontrollers used in COTS sensor nodes include between 8 and<br />

512 Kbytes of non-volatile program memory (typically FLASH memory) and<br />

up to 4 Kbytes of volatile SRAM. Some additionally provide up to 4 Kbytes of<br />

non-volatile general-purpose memory, such as EEPROM. To per<strong>for</strong>m memoryintensive<br />

algorithms, some designs add external data memories. The address<br />

bus of 8-bit microcontrollers is typically only 16-bit wide, allowing to address<br />

only 64 Kbytes. There<strong>for</strong>e the entire data memory (including heap, static data


20 Chapter 2. <strong>Wireless</strong> <strong>Sensor</strong> <strong>Networks</strong><br />

segment, and runtime stack) is constrained to this amount. The few designs<br />

with a larger memories space, organize their data memory into multiple pages<br />

of 64 Kbytes. Then one page serves as runtime memory (which holds the runtime<br />

stack and program variables) while the others can be used as unstructured<br />

data buffers. Current SOC designs typically feature RAM in the order of a few<br />

Kbytes.<br />

Memory occupies a significant fraction of the chip real-estate. Since the die<br />

area is a dominating cost factor in chip design, memories contribute significantly<br />

to sensor-node costs. This is true <strong>for</strong> COTS microcontroller designs as<br />

well as custom-designed sensor-node SOCs. For example, 3 Kbytes of RAM of<br />

the Spec sensor-node SOC occupy 20-30% of the total die area of 1mm 2 , not including<br />

the memory controller [60]. For comparison: the analog RF transmitter<br />

circuitry in the Spec mote requires approximately the same area as one Kbyte of<br />

SRAM, as can bee seen in Fig. 2.1 (a). In general, FLASH memory has a significant<br />

density advantage over SRAM. Modern FLASH technology produces storage<br />

densities higher than 150 Kbytes per square millimeter against the record of<br />

60 Kbytes per square millimeter <strong>for</strong> optimized SRAM dedicated to per<strong>for</strong>ming<br />

specific functions [60]. Fig. 2.1 (b) shows a comparison of the occupied die sizes<br />

<strong>for</strong> 60 Kbytes of FLASH memory against 4 byte of SRAM in a commercial 8-bit<br />

microcontroller design.<br />

Unpaged RAM beyond 64 Kbytes also requires an address bus larger than the<br />

standard 16-bit (and potentionally memory-management units), which results<br />

in a disproportional increase in system complexity. Because of the a<strong>for</strong>ementioned<br />

reasons we expect that even in the future a significant amount of all cost<br />

and size constrained sensor nodes (which we belive will be a significant amount<br />

of all sensor nodes) will not posses data memories exceeding 64 Kbytes.<br />

<strong>Wireless</strong> Communication Subsystems<br />

Common to all wireless sensor networks is that they communicate wirelessly.<br />

For wireless communication among the nodes of the network, most sensor networks<br />

employ radio frequency (RF) communication, although light and sound<br />

have also been utilized as physical communication medium.<br />

Radio Frequency Communication. RF communication has some desirable properties<br />

<strong>for</strong> wireless sensor networks. For example, RF communication does not<br />

require a line of sight between communication partners. The omnidirectional<br />

propagation of radio waves from the sender’s antenna is frequently used to implement<br />

a local broadcast communication scheme, where nodes communicate<br />

with other nodes in their vicinity. The downside of omnidirectional propagation<br />

is, however, that the signal is diminished as its energy spreads geometrically<br />

(known as free space loss).<br />

Various wireless transceivers have been used in sensor-node designs. Some<br />

transceivers provide an interface <strong>for</strong> adjusting the transmit power, which gives<br />

coarse-grained control over the transmission radius, if the radio propagation<br />

characteristics are known. Very simple RF transceivers, such as the TR 1000<br />

from RF Monolithics, Inc merely provide modulation and demodulation of bits<br />

on the physical medium (the “Ether”). Bit-level timing while sending bit strings<br />

as well as timing recovery when receiving has to be per<strong>for</strong>med by a host proces-


2.3. <strong>Sensor</strong> Nodes 21<br />

CPU<br />

RAM<br />

1mm<br />

Radio<br />

(a) Die layout of the Spec sensor-node<br />

SOC (8-bit RISC microcontroller core)<br />

with 3 Kbytes of memory (6 banks of<br />

512 byte each) and a 900 MHz RF transmitter<br />

[60].<br />

FLASH<br />

RAM<br />

(b) Die layout of a commercial 8-bit<br />

microcontroller (HCS08 core from<br />

Freescale Semiconductors) with<br />

4 Kbytes of RAM and 60 Kbytes of<br />

FLASH memory [103].<br />

Figure 2.1: Die layouts of 8-bit microcontrollers. Memory occupies a significant<br />

fraction of the chip real-estate.<br />

sor. Since the Ether is shared between multiple nodes and the radio has a fixed<br />

transmission frequency, it effectively provides a single broadcast channel. Such<br />

radios allow to implement the entire network stack (excluding the physical layer<br />

but including framing, flow control, error detection, medium access, etc.) according<br />

to application needs. However, the required bit-level signal processing<br />

can be quite complex and occupy a significant part of the available computing<br />

resources of the central processor. Such a radio was used in the early Berkeley<br />

Mote designs [57, 59].<br />

More sophisticated radios, such as the CC1000 from Chipcon AS used in more<br />

recent Berkeley Mote designs, provide modulation schemes that are more resilient<br />

to transmission errors in noisy environments. Additionally, they provide<br />

a software controllable frequency selection during operation.<br />

Finally, the most sophisticated radio transceivers used in sensor nodes, such<br />

as radio modules compliant to the Bluetooth and IEEE 802.15.4 standards, can<br />

be characterized as RF data modems. These radio modules implement the MAC<br />

layer and provide its functionality through a standardized interface to the node’s<br />

main processor. In Bluetooth, <strong>for</strong> example, the physical transport layer <strong>for</strong> interface<br />

between radio and host CPU (the so-called Host Controller Interface, HCI)<br />

is defined <strong>for</strong> UART, RS-232, and USB. This allows to operate the Bluetooth module<br />

as an add-on peripheral to the node’s main CPU. The BTnode, iMote, and<br />

Mica-Z sensor nodes, <strong>for</strong> example, use such RF data modems.<br />

Some node designs provide multiple radio interfaces, typically with different<br />

characteristics. An example is the BTnode [123], which provides two short-range<br />

radios. One is a low-power, low-bandwidth radio, while the other one is a high-


22 Chapter 2. <strong>Wireless</strong> <strong>Sensor</strong> <strong>Networks</strong><br />

power but also high-bandwidth Bluetooth radio. The ZebraNet [67] sensor node<br />

deploys a short-range and a long-range radio.<br />

Optical Communication. Light as a communication medium has radically different<br />

characteristics. One significant drawback is that because of the mostly<br />

directional propagation of light, optical communication requires a line of sight.<br />

Also, ambient light can interfere with the signal and may lead to poor link quality.<br />

There<strong>for</strong>e optical communication may be suitable only <strong>for</strong> specific applications.<br />

However, it has several benefits over RF communication. Because of<br />

lower path loss it is more suitable <strong>for</strong> long-range communications. Also, light<br />

signals do not require sophisticated modulation and demodulation, thus optical<br />

transceivers can be realized with less chip complexity and are more energy<br />

efficient compared to radio transceivers. Also, they can be built very small.<br />

At the University of Cali<strong>for</strong>nia at Berkeley, the Smart Dust [68, 120] project<br />

examines the use of laser light <strong>for</strong> communication in their Smart Dust sensor<br />

nodes. The project aims to explore the limits of sensor-node miniaturization by<br />

packing all required subsystems (including sensors, power supply, and wireless<br />

communication) into a 1 mm 3 node (see Fig. 2.2 <strong>for</strong> a conceptual diagram). Optical<br />

communication has been chosen to avoid the comparatively large dimensions<br />

<strong>for</strong> antennas. A receiver <strong>for</strong> optical signals basically consists of a simple<br />

photodiode. It allows the reception of data modulated onto a laser beam emitted<br />

by another node or a base station transceiver (BST). For data transmissions,<br />

two schemes are being explored in Smart Dust: active and passive transmission.<br />

For active transmission, the sensor node generatesr a lase beam with a laser<br />

diode. The beam is then modulated using a MEMS steerable mirror. For passive<br />

transmission, the node modulates an unmodulated laser beam with a so-called<br />

corner-cube retroreflector (CCR). The CCR is a MEMS structure of perpendicular<br />

mirrors, one of which is deflectable. Incident light is reflected back to the<br />

light source, unless deflected. Several prototypes of the optical transceiver system<br />

and entire Smart Dust sensor nodes have been built. We will present one of<br />

them in the following section.<br />

<strong>Sensor</strong>s, <strong>Sensor</strong> Boards and <strong>Sensor</strong> Interface<br />

Application-specific sensor-node designs typically include all or most sensors.<br />

General-purpose nodes, on the other hand, include only few or no sensors at<br />

all, but provide a versatile external interface instead. This approach is beneficial<br />

since the required sensor configurations strongly depend on the target application.<br />

The external interface allows to attach various sensors or actuators directly<br />

or to attach a preconfigured sensor board. The sensors most commonly found in<br />

sensor node and sensor board designs are <strong>for</strong> visible light, infrared, audio, pressure,<br />

temperature, acceleration, position (e.g., GPS). Less typical sensor types<br />

include hygrometers, barometers, magnetometers, oxygen saturation sensors,<br />

and heart-rate sensors. Simple analog sensors are sampled by the processor via<br />

an analog-to-digital converter (ADC). More sophisticated sensors have digital<br />

interfaces, such as serial ports (e.g., USARTs) or bus systems (e.g., I2C). Over<br />

this digital interface a proprietary application protocol is run <strong>for</strong> sensor configuration,<br />

calibration and sampling. The processor of the sensor node typically<br />

implements these protocols in software. Actuators found in WSN include LEDs,


2.3. <strong>Sensor</strong> Nodes 23<br />

<strong>Sensor</strong>s<br />

Mirrors<br />

Analog I/O, DSP, Control<br />

Passive Transmitter with<br />

Corner-Cube Retroreflector<br />

1-2mm<br />

Interrogating<br />

Laser Beam<br />

Laser Lens Mirror<br />

Active Transmitter<br />

with Beam Steering<br />

Power Capacitor<br />

Solar Cell<br />

Thick-Film Battery<br />

Incoming Laser<br />

Communication<br />

Photodetector Photodetector and and Receiver<br />

Receiver<br />

Figure 2.2: Smart Dust conceptual diagram (from [119]).<br />

buzzers, and small speakers.<br />

2.3.3 Selected <strong>Sensor</strong>-Node Hardware Plat<strong>for</strong>ms<br />

Today there are a number of sensor nodes available <strong>for</strong> use in WSN research and<br />

development. Some designs have been commercialized and are available even<br />

in large numbers. Others designs have been used in limited numbers only in research<br />

projects. Still other designs are alpha versions with just a few prototypes<br />

built or which exist only in hardware simulators.<br />

Depending on their intended application scenarios, sensor nodes have to meet<br />

vastly different (physical) application requirements, like sensor configurations<br />

and durability. However, we focus mainly on the characteristics that are relevant<br />

<strong>for</strong> developing and running software. In the following we present a few<br />

selected sensor nodes which represent various design points and different stages<br />

of maturity. An overview of available sensor node plat<strong>for</strong>ms can be found in [58]<br />

and in [111].<br />

BTnodes<br />

The BTnode is an autonomous wireless communication and computing plat<strong>for</strong>m<br />

based on a Bluetooth radio module and a microcontroller. The design has<br />

undergone two major revisions from the initial prototype. The latest generation<br />

uses a new Bluetooth subsystem and adds a second low-power radio, which is<br />

identical to those used in Berkeley Motes (see below). For a detailed description<br />

of all generations of BTnodes refer to [20].


24 Chapter 2. <strong>Wireless</strong> <strong>Sensor</strong> <strong>Networks</strong><br />

GPIO Analog Serial IO<br />

Bluetooth<br />

ATmega128L<br />

Module Microcontroller<br />

Clock/Timer LED’s<br />

SRAM<br />

Power<br />

Supply<br />

Figure 2.3: The BTnode rev2 system overview shows the sensor node’s four<br />

main components: radio, microcontroller, external memory, and<br />

power supply. Peripherals, such as sensors, can be attached through<br />

the versatile general-purpose interfaces.<br />

BTnodes have no integrated sensors, since individual sensor configurations<br />

are typically required depending on the application (see Fig. 2.3). Instead, with<br />

its many general-purpose interfaces, the BTnode can be used with various peripherals,<br />

such as sensors, but also actuators, DSPs, serial devices (like GPS receivers,<br />

RFID readers, etc.), and user interface components. An interesting property<br />

of this plat<strong>for</strong>m is its small <strong>for</strong>m factor of 6x4 cm while still maintaining a<br />

standard wireless interface.<br />

6 cm<br />

4 cm<br />

Figure 2.4: The BTnode rev2 hardware features a Bluetooth module, antenna,<br />

LEDs, and power as well as interface connectors on the front (left),<br />

and an 8-bit microcontroller and external memory on the back (right).<br />

The second revision of the BTnode hardware (see Fig. 2.4) is built around an<br />

Atmel ATmega128L microcontroller with on-chip memory and peripherals. The


2.3. <strong>Sensor</strong> Nodes 25<br />

microcontroller features an 8-bit RISC core delivering up to 8 MIPS at a maximum<br />

of 8 MHz. The on-chip memory consists of 128 kbytes of in-system programmable<br />

Flash memory, 4 kbytes of SRAM, and 4 kbytes of EEPROM. There<br />

are several integrated peripherals: a JTAG interface <strong>for</strong> debugging, timers, counters,<br />

pulse-width modulation, 10-bit analog-digital converter, I2C bus, and two<br />

hardware UARTs. An external low-power SRAM adds an additional 240 kbytes<br />

of data memory to the BTnode system. A real-time clock is driven by an external<br />

quartz oscillator to support timing updates while the device is in low-power<br />

sleep mode. The system clock is generated from an external 7.3728 MHz crystal<br />

oscillator.<br />

An Ericsson Bluetooth module is connected to one of the serial ports of the<br />

microcontroller using a detachable module carrier, and to a planar inverted F<br />

antenna (PIFA) that is integrated into the circuit board.<br />

Four LEDs are integrated, mostly <strong>for</strong> the convenience of debugging and monitoring.<br />

One analog line is connected to the battery input and allows to monitor<br />

the battery status. Connectors that carry both power and signal lines are provided<br />

and can be used to add external peripherals, such as sensors and actuators.<br />

Berkeley Motes<br />

An entire family of sensor nodes, commonly referred to as Berkeley Motes or<br />

motes <strong>for</strong> short, has been developed out of a research project at the University<br />

of Cali<strong>for</strong>nia at Berkeley [61]. Berkeley Motes run the very popular TinyOS operating<br />

system. These were the first sensor nodes to be successfully marketed<br />

commercially. They are manufactured and marketed by a third-party manufacturer,<br />

Crossbow [34]. Crossbow also offers development kits and training<br />

services beyond the mere sensor devices. Because of the early availability of the<br />

hardware and because of the well maintained and documented TinyOS operating<br />

system, many applications have been realized on Berkeley Motes. They<br />

have a very active user base and are often referred to as the de facto standard<br />

plat<strong>for</strong>m <strong>for</strong> sensor networks.<br />

The various members of the Berkeley Mote family (known as Mica, Mica2,<br />

Mica2Dot, Micaz, and Cricket) offer varying radio and sensor configurations at<br />

different sizes. Like the BTnode, they all feature an Atmel ATmega128L 8-bit microcontroller<br />

and allow to connect different sensor boards. Unlike the BTnode,<br />

however, they have no external SRAM but instead offer 512 Kbyte of nonvolatile<br />

memory. All Berkeley motes posses simple wireless radios with synchronous bit<br />

or packet interfaces, which require intensive real-time processing on the node’s<br />

CPU.<br />

Smart Dust<br />

The Smart Dust project [120] aims to explore the limits of sensor-node miniaturization<br />

by packing all required subsystems (including sensors, power supply,<br />

and wireless communication) into a 1 mm 3 node. The applications of such nodes<br />

are massively distributed sensor networks, such as military defense networks<br />

that could be rapidly deployed by unmanned aerial vehicles, and networks <strong>for</strong><br />

environmental monitoring.


26 Chapter 2. <strong>Wireless</strong> <strong>Sensor</strong> <strong>Networks</strong><br />

In 2002, a 16 mm 3 solar-powered prototype with bidirectional optical communication<br />

has been demonstrated in [119] (see Fig. 2.2). The system consists of<br />

three dice, which can be integrated into a single package of 16 mm 3 . A 2.6 mm 2<br />

die contains the solar-cell array, which allows the node to function at light levels<br />

of approximately one sun. The second die contains a four-quadrant corner-cube<br />

retroreflector (CCR), allowing it to be used in a one-to-many network configuration,<br />

as discussed previously. The core of the system is a 0.25 mm 2 CMOS<br />

ASIC containing the controller, optical receiver, ADC, photo sensor, and oscillator.<br />

This die is depicted in Fig. 2.6.<br />

The prototype node is not programmable, but is instead operated with a 13state<br />

finite state machine in hardware. The demonstrated node per<strong>for</strong>ms a fixed<br />

schedule of operations. First it toggles between sensors and initiates ADC conversion.<br />

When the conversion is complete the result is passed serially to the<br />

CCR <strong>for</strong> optical transmission. Next, the most recent byte received by the optical<br />

transceiver is echoed. Upon the completion of the transmission the cycle<br />

repeats.<br />

The presented Smart Dust node is a prototype with many features of an actual<br />

sensor node still missing or none-functional in a realistic environment. The<br />

solar cells do not provide enough energy to run the node in less then optimal circumstances.<br />

The light required <strong>for</strong> powering the node interferes with the optical<br />

communication. It has no packaging, so the actual node would be significantly<br />

bigger. An acceleration sensor was initially intended to be included into the design<br />

but could not due to problems with the final processing steps. Finally, the<br />

node is not programmable and thus lacks the flexibility <strong>for</strong> customization. A<br />

programmable node would occupy significantly more die space to host the programmable<br />

controller as well as data and program memories (which it currently<br />

lacks).<br />

On the other hand, the presented node has shown to some degree what can be<br />

expected in the not-too-far future. Future motes are expected to be yet smaller<br />

through higher levels of integration, have more functionality by means of fully<br />

programmable controllers, and incorporate more types of sensors. A new development<br />

process has already been theorized (and partially tested successfully)<br />

which will yield a 6.6 mm 3 node with only two dice.<br />

SNAP<br />

The sensor network asynchronous processor (SNAP) [39, 66] is a novel processor<br />

architecture designed specifically <strong>for</strong> use in low-power wireless sensor-network<br />

nodes. The design goals are low power consumption and hardware support <strong>for</strong><br />

the predominant event-driven programming model, which is used in TinyOS<br />

and the BTnode system software.<br />

The processor has not yet been built but low-level simulation results of the<br />

processor core exist. SNAP is based on 16-bit RISC core with an extremely<br />

low-power idle state and very low wakeup response latency. The processor instruction<br />

set is optimized to support event scheduling, pseudo-random number<br />

generation, bit-field operations, and radio/sensor interfaces. SNAP has a<br />

hardware event queue and event coprocessors, which allow the processor to<br />

avoid the overhead of event buffering in the operating system software (such as


2.4. Embedded Systems 27<br />

(a) Actual size of the sensor node in<br />

comparison to a coin.<br />

3<br />

Solar Cell Array<br />

CCR<br />

Receiver<br />

XL<br />

CMOS<br />

IC Photosensor<br />

(b) Scanning electron micrograph of the node<br />

to the left.<br />

Figure 2.5: A partially functional Smart Dust prototype-node with a circumscribed<br />

volume of 16 mm 3 as presented in [119].<br />

330µm<br />

Photo<br />

sensor<br />

XL<br />

ADC<br />

Digital<br />

1V Vdd<br />

Osc<br />

FSM<br />

Analog<br />

1V Vdd<br />

GND LED CCR<br />

Receiver<br />

Receiver<br />

2.0V Vdd<br />

1mm<br />

Figure 2.6: Die layout of the Smart Dust prototype presented in [119]. The detail<br />

shown is 1mm x 330um.<br />

task schedulers and external interrupt servicing). The SNAP processor runs of a<br />

0.6V power supply consuming about 24pJ per instruction at 28 MIPS. It features<br />

4 Kbyte of program memory and another 4 Kbyte of data memory.<br />

2.4 Embedded Systems<br />

<strong>Wireless</strong> sensor nodes have been considered to be a subclass of embedded systems,<br />

namely networked embedded systems (see, <strong>for</strong> example, [4, 20, 32, 45, 57,<br />

89]). In this section we will present general embedded-systems characteristics<br />

and relate them to those of wireless sensor nodes.<br />

2.4.1 Characteristics of Embedded Systems<br />

Unlike general-purpose computers, embedded systems are special-purpose<br />

computers engineered to per<strong>for</strong>m one or a few predefined tasks. They are typically<br />

embedded into a larger device or machine, hence the name. Typical pur-


28 Chapter 2. <strong>Wireless</strong> <strong>Sensor</strong> <strong>Networks</strong><br />

poses of embedded systems are monitoring or controlling the devices they are<br />

embedded in, per<strong>for</strong>ming computational operations on behalf of them, as well<br />

as providing interfaces to other systems, the environment (through sensors and<br />

actuators), and users. Often multiple embedded systems are networked, such as<br />

in automobiles and aircrafts.<br />

2.4.2 Diversity of Embedded Systems<br />

Embedded systems are very diverse, both in their application domains as<br />

well as in their requirements. Examples of embedded-system applications are<br />

industrial-machine controllers, cruise-controls and airbag controllers in automobiles,<br />

medical equipment, digital video and still cameras, controllers in washingmachines<br />

and other household appliances, fly-by-wire systems, vending machines,<br />

toys, and sensing (typically wired) monitoring systems. Depending on<br />

the application domain, embedded systems can have very diverse requirements,<br />

such as high reliability (e.g., in medical systems), high-per<strong>for</strong>mance data processing<br />

(e.g., image processing in cameras), low cost (e.g., in commodity devices<br />

such as remote controls), and real-time behavior (e.g., in fly-by-wire systems and<br />

airbag controllers). In fact, there does not seem to be a single set of requirements<br />

applicable to all variants of embedded systems. For example, a digital-camera<br />

controller typically requires high per<strong>for</strong>mance at a very low cost and low power<br />

consumption. In contrast, medical systems require high reliability and real-time<br />

behavior while per<strong>for</strong>mance, low cost, and low power consumption may not be<br />

an issue.<br />

2.4.3 <strong>Wireless</strong> <strong>Sensor</strong> Nodes<br />

<strong>Sensor</strong> nodes have also been considered embedded systems. Though there are<br />

some differences to traditional embedded systems, they also share many characteristics<br />

and use very similar technology. The two most obvious differences<br />

are that firstly, sensor nodes are typically not embedded into other machines<br />

or devices but are autonomous and self contained. And secondly, traditional<br />

distributed embedded systems typically use wired communication as it is more<br />

reliable.<br />

Apart from that, sensor nodes per<strong>for</strong>m tasks similar to embedded systems and<br />

have similar characteristics. Just like embedded systems, sensor nodes per<strong>for</strong>m<br />

various monitoring and control tasks and often per<strong>for</strong>m significant data processing.<br />

<strong>Sensor</strong> nodes also provide interfaces (to other nodes and the backend<br />

<strong>for</strong> tasking), typically through the air interface. As we have argued in Sect. 3.1,<br />

most applications of wireless sensor nodes do share a common set of requirements,<br />

probably the most important of which are resource-efficiency, reliability,<br />

and reactivity.<br />

2.5 Summary and Outlook<br />

In this chapter we have presented various applications of WSNs and their requirements<br />

on a technical hard and software solution. Often the size and cost


2.5. Summary and Outlook 29<br />

constrains of sensor nodes preclude the use of powerful energy supplies. To ensure<br />

adequate sensor-network lifetimes it is often necessary to strictly confine<br />

the nodes’ energy consumption. As a consequence of these three restrictions<br />

(cost, size, and energy consumption), most modern sensor nodes are highly integrated<br />

and posses very little resources. Typical configurations feature 8-bit microcontrollers<br />

running at speeds of a few MIPS, tenths of kilobytes of program<br />

memory, and a few kilobytes of data memory. Though not all WSN applications<br />

necessitate such highly resource-constrained nodes, many indeed do.<br />

The resources provisions of such integrated devices are very different compared<br />

to traditional computing systems. Also, they are used in a fundamentally<br />

different manner. WSNs are in close interaction with the environment, they are<br />

self-organized and operate without human intervention rather than relying on<br />

a fixed background infrastructure configured by system administrators. These<br />

characteristics pose new challenges on all levels of the traditional software stack.<br />

New resource-efficient and fault-tolerant algorithms and protocols need to<br />

be developed. Classical middleware concepts need to be reconsidered in order<br />

to allow the utilization of application knowledge. New operating systems<br />

are required to economically manage the limited sensor-node resources while<br />

incurring as little resource overhead as possible themselves. Traditional operating<br />

system abstractions (such as processor sharing and dynamic and virtual<br />

memory management), which rely on hardware components of classical machine<br />

architectures (like memory management units and secondary storage) can<br />

not be easily used in sensor networks. This also requires revisiting the basic<br />

application-programming models and abstractions, such as tasks and inter-task<br />

communication. In the next chapter we will present the state-of-the-art in programming<br />

models and runtime environments <strong>for</strong> wireless sensor networks.


30 Chapter 2. <strong>Wireless</strong> <strong>Sensor</strong> <strong>Networks</strong>


3 <strong>Programming</strong> and Runtime<br />

Environments<br />

Developing a sensor network application typically requires to program individual<br />

sensor nodes. Today, programming of individual sensor nodes is mostly<br />

per<strong>for</strong>med by programmers directly. Even though research has made much<br />

progress in distributed middleware and high-level configuration languages <strong>for</strong><br />

sensor nodes, typically most of the application code still has to be specified by<br />

the hand of a programmer.<br />

To support application programming, several programming frameworks exist<br />

<strong>for</strong> sensor nodes. <strong>Programming</strong> frameworks provide a standard structure<br />

to develop application programs, typically <strong>for</strong> a specific application domain,<br />

<strong>for</strong> example, Graphical User Interface programming, or, in our case, sensornode<br />

programming. <strong>Programming</strong> frameworks <strong>for</strong> sensor nodes consist of a<br />

runtime environment with reusable code and software components, programming<br />

languages, and software tools to create and debug executable programs<br />

(see Fig. 3.1). The conceptual foundation of a programming framework is a programming<br />

model. The programming model defines the conceptual elements in<br />

which programmers think and structure their programs. These elements are<br />

operating-system abstractions (such as threads, processes, dynamic memory,<br />

etc.) and language abstractions (such as functions, variables, arguments and<br />

return parameters of procedural programming languages). Other elements are<br />

abstractions <strong>for</strong> frequently used (data) objects (such as files, network connections,<br />

tracking targets, etc.), as well as convenient operations and algorithms on<br />

those objects.<br />

Programs specified based on high-level programming models, however, do<br />

not directly execute on the sensor-nodes hardware. Rather, they require a software<br />

layer to provide them with a runtime environment. That runtime environment<br />

bridges the gap between the high-level programming model and the lowlevel<br />

execution model of the processor. It typically consists of middleware components,<br />

code libraries, OS system calls, and compiler-generated code, which<br />

together provide the implementations of the model’s abstractions. Naturally, a<br />

programming model with more and more expressive abstractions will require<br />

more support from the runtime environment.<br />

The explicit goal of traditional computing systems is to accommodate a wide variety<br />

of applications from different domains. There<strong>for</strong>e, they typically provide a<br />

“thick” and powerful system software that supports rich programming models<br />

and many abstractions. From this variety, application designers can (and must)<br />

choose a model that suits their applications needs best. <strong>Sensor</strong>-node plat<strong>for</strong>ms,<br />

however, are often subjected to severe resource constraints. There<strong>for</strong>e they can<br />

af<strong>for</strong>d only a thin system software layer and can thus offer only a limited programming<br />

model. The designers of programming frameworks <strong>for</strong> sensor-node


32 Chapter 3. <strong>Programming</strong> and Runtime Environments<br />

<strong>Programming</strong> Framework<br />

<strong>Programming</strong> Tools<br />

<strong>Programming</strong> <strong>Model</strong><br />

<strong>Programming</strong> Languages<br />

Runtime Environment<br />

System Software<br />

− Middleware<br />

− Libraries<br />

− Operating System Kernel<br />

Hardware<br />

Compiler, Linker, Debugger<br />

Language abstractions<br />

C, C++, nesC<br />

OS and data abstractions<br />

RPC, EnviroTrack, TinyDB<br />

libC, Berkeley sockets<br />

Linux, TinyOS<br />

PCs, BTnodes<br />

Figure 3.1: <strong>Programming</strong> frameworks <strong>for</strong> sensor nodes consist of reusable code<br />

and software components, programming languages, software tools<br />

to create and debug executable programs.<br />

plat<strong>for</strong>ms must make an a priori decision which models, abstractions, and system<br />

services to support. They are faced with the dilemma to provide an expressive<br />

enough programming model without imposing too heavy a burden on the<br />

scarce system resources. Resources spent <strong>for</strong> the system software are no longer<br />

available to application programmers.<br />

In this chapter we will review state-of-the-art programming models and the<br />

programming abstractions supported in current system software <strong>for</strong> sensor<br />

nodes. We start by presenting basic requirements that must be met by the system<br />

software and application programs alike in Sect. 3.1. Then, in Sect. 3.2,<br />

we present the three basic programming models that have been proposed <strong>for</strong><br />

sensor-node programming, that is, the event-driven model, the multi-threaded<br />

model, and the control loop. We continue our discussion with Sections 3.3<br />

and 3.4 on dynamic memory management and on process models, respectively.<br />

<strong>Based</strong> on our initial requirements (from Sect. 3.1) we analyze these programming<br />

models and abstractions with respect to the runtime support they necessitate.<br />

Finally, in Sect. 3.4, we summarize state-of-the-art programming frameworks<br />

(most of which we have introduced in the previous sections already in order to<br />

highlight certain aspects) and present in greater detail two programming frameworks,<br />

which represent variations of the basic event-driven model.<br />

3.1 System Requirements<br />

In the last chapter we have discussed the unique characteristics of sensor networks.<br />

These characteristics result in a unique combination of constraints and<br />

requirements that significantly influence the software running on sensor nodes.<br />

But this influence is not limited to the choice of application algorithms and data<br />

structures. The nodes’ system software, that is, the operating system and other


3.1. System Requirements 33<br />

software to support the operation of the sensor node, is affected as well. In this<br />

section we present three main requirements of WSN software. While this is not<br />

a comprehensive list of topics, it does identify the most important topics with<br />

regard to sensor-node programming.<br />

3.1.1 Resource Efficiency<br />

A crucial requirement <strong>for</strong> all WSN programs is the efficient utilization of the<br />

available resources, <strong>for</strong> example, energy, computing power, and data storage.<br />

Since all sensor nodes are untethered by definition, they typically rely on the<br />

finite energy reserves from a battery. This energy reserve is the limiting factor of<br />

the lifetime of a sensor node. Also, many sensor-node designs are severely constrained<br />

in fundamental computing resources, such as data memory and processor<br />

speeds. <strong>Sensor</strong> nodes typically lack some of the system resources typically<br />

found in traditional computing systems, such as secondary storage or arithmetic<br />

co-processors. This is particularly the case <strong>for</strong> nodes used in applications that<br />

require mass deployments of unobtrusive sensor nodes, as these applications<br />

prompt small and low-cost sensor-node designs.<br />

In addition to energy-aware hardware design, the node’s system software<br />

must use the available memory efficiently and must avoid CPU-cycle intense<br />

operations, such as copying large amounts of memory or using floating-point<br />

arithmetic. Various software-design principles to save energy have been proposed<br />

[41]. On the algorithmic level, localized algorithms are distributed algorithms<br />

that achieve a global goal by communicating with nodes in a close neighborhood<br />

only. Such algorithms can conserve energy by reducing the number<br />

and range of radio messages sent. Adaptive fidelity algorithms allow to trade<br />

the quality of the sensing result against resource usage. For example, energy can<br />

be saved by reducing the sampling rate of a sensor and there<strong>for</strong>e increasing the<br />

periods where it can be switched off.<br />

Resource constraints clearly limit the tasks that can be per<strong>for</strong>med on a node.<br />

For example, the computing and memory resources of sensor nodes are often too<br />

limited to per<strong>for</strong>m typical signal processing tasks like FFT and signal correlation.<br />

Hence, clustered architectures were suggested (e.g., [117]), where the clusterheads<br />

are equipped with more computing and memory resources leading to<br />

heterogeneous sensor networks.<br />

Resource constraints also greatly affect operating system and language features<br />

<strong>for</strong> sensor nodes. The lack of secondary storage systems precludes virtualmemory<br />

architectures. Also, instead of deploying strictly layered software architectures,<br />

designers of system software often choose cross-layer designs to optimize<br />

resource efficiency. <strong>Programming</strong> frameworks <strong>for</strong> sensor nodes should<br />

support application designers to specify resource efficient and power-aware programs.<br />

3.1.2 Reliability<br />

<strong>Wireless</strong> sensor networks are typically deployed in remote locations. As software<br />

failures are expensive or impossible to fix, high reliability is critical. There<strong>for</strong>e,<br />

algorithms have been proposed that are tolerant to single node and net-


34 Chapter 3. <strong>Programming</strong> and Runtime Environments<br />

work failures. However, immanent bugs in the nodes’ system software may<br />

lead to system crashes that may render an entire WSN useless. Particularly if<br />

many or all nodes of the network run identical software or if cluster heads are<br />

affected, the network may not recover.<br />

<strong>Programming</strong> frameworks <strong>for</strong> sensor nodes should support application designers<br />

in the specification of reliable and, if possible, verifiable programs. Some<br />

of the standard programming mechanisms, like dynamic memory management<br />

and multi-threading, are known to be hard to handle and error prone. There<strong>for</strong>e<br />

some programming frameworks deliberately choose not to offer these services<br />

to the application programmer.<br />

3.1.3 Reactivity<br />

The main application of WSNs is monitoring the real world. There<strong>for</strong>e sensor<br />

nodes need to be able to detect events in the physical environment and to react<br />

to them appropriately. Also, due to the tight collaboration of the multiple<br />

nodes of a WSN, nodes need to react to network messages from other nodes.<br />

Finally, sensor nodes need to be able to react to a variety of internal events, such<br />

as timeouts from the real-time clock, interrupts generated by sensors, or low<br />

battery indications.<br />

In many respects sensor nodes are reactive systems. Reactive systems are computer<br />

systems that are mainly driven by events. Their progress as a computer<br />

system depends on external and internal events, which may occur unpredictably<br />

and unexpectedly at any time, in almost any order, and at any rate. Indeed, the<br />

philosophy of sensor-node programming frameworks mandates that computations<br />

are only per<strong>for</strong>med in reaction to events and that the node remains in sleep<br />

mode otherwise in order to conserve power.<br />

On the occurrence of events, wireless sensor nodes react by per<strong>for</strong>ming computations<br />

or by sending back stimuli, <strong>for</strong> example, through actuators or by sending<br />

radio messages. <strong>Sensor</strong> nodes need to be able to react to all events, no matter<br />

when and in which order they occur. Typically reactions to events must be<br />

prompt, that is, they must not be delayed arbitrarily. The reactions to events<br />

may also have real-time requirements. Since the handling of events is one of<br />

the main tasks of a sensor node, programming frameworks <strong>for</strong> sensor nodes<br />

should provide expressive abstractions <strong>for</strong> the specification of events and their<br />

reactions.<br />

3.2 <strong>Programming</strong> <strong>Model</strong>s<br />

<strong>Sensor</strong> nodes are reactive systems. Programs follow a seemingly simple pattern:<br />

they remain in sleep mode until some event occurs, on which they weak<br />

up to react by per<strong>for</strong>ming a short computation, only to return to sleep mode<br />

again. Three programming models have been proposed to support this pattern<br />

while meeting the requirements specified previously. These models are<br />

the event-driven model, the multi-threaded model, and the control-loop model.<br />

The event-driven model is based on the event-action paradigm, while the multithreaded<br />

model and the control loop are based on polling <strong>for</strong> events (in a block-


3.2. <strong>Programming</strong> <strong>Model</strong>s 35<br />

ing and non-blocking fashion, respectively).<br />

3.2.1 Overview<br />

In the control-loop model there is only a single flow of control, namely the control<br />

loop. In this loop, application programmers must repeatedly poll <strong>for</strong> the events<br />

that they are interested in and handle them on their detection. Polling has to be<br />

non-bocking in order not to bring the entire program to a halt while waiting <strong>for</strong><br />

a specific event to occur. Then, the computational reaction to an event must be<br />

short in order not to excessively delay the handling of next events.<br />

In the multi-threaded model, programmers also poll <strong>for</strong> events and handle them<br />

on detection. However, polling can be blocking since programs have several<br />

threads with individual flows of control. For the same reason the duration of<br />

a computational reaction is not bounded. When one thread is blocking or per<strong>for</strong>ming<br />

long computations, the operating system takes care of scheduling other<br />

threads concurrently, that is, by sharing the processor in time multiplex.<br />

In the event-driven model the system software remains the program’s control<br />

flow and only passes it shortly to applications to handle events. The system<br />

software is responsible to detect the occurrence of an event and then to “call<br />

back” a specific event-handling function in the application code. These functions<br />

are called actions (or event handlers). They are the basic elements of which<br />

applications are composed of. Only within these actions do applications have<br />

a sequential flow. The scheduling of actions, however, is dictated by the occurrence<br />

of events.<br />

These models differ significantly in their expressiveness, that is, the support<br />

they provide to a programmer. They also require different levels of support<br />

from the runtime environment and thus the amount of system resources that are<br />

allocated <strong>for</strong> the system software. In the following we will analyze these three<br />

models regarding their expressiveness and inherent resource consumption.<br />

3.2.2 The Control Loop<br />

Some sensor-node plat<strong>for</strong>ms (such as the Embedded <strong>Wireless</strong> Module nodes described<br />

in [97] and early versions of Smart-Its nodes [124]) provide programming<br />

support only as a thin software layer of drivers between the hardware<br />

and the application code. Particularly, these systems typically do not provide a<br />

programming abstraction <strong>for</strong> event-handling or concurrency, such as actions or<br />

threads. Rather they only provide a single flow of control.<br />

In order to guarantee that a single-threaded program can handle and remains<br />

reactive to all events, the program must not block to wait <strong>for</strong> any single<br />

event. Instead, programmers typically poll <strong>for</strong> the occurrence of events in<br />

a non-blocking fashion. Among experienced programmers it is an established<br />

programming pattern to group all polling operations in a single loop, the socalled<br />

control loop. (In this respect the control-loop model is much more a programming<br />

pattern or approach <strong>for</strong> sensor nodes–or, more generally, <strong>for</strong> reactive<br />

systems–rather than a programming model with explicitly supported abstractions.)<br />

The control loop is an endless loop, which continuously polls <strong>for</strong> the<br />

occurrence of events in a non-blocking fashion. If the occurrence of an event


36 Chapter 3. <strong>Programming</strong> and Runtime Environments<br />

has been detected, the control loop invokes a function handling the event. For<br />

optimizations actual programs often depart from this pattern. The pseudo-code<br />

in Prog. 3.1 depicts the principle of a control loop.<br />

Program 3.1: A simple control loop.<br />

1 while( true ) { // control loop, runs <strong>for</strong>ever<br />

2 if( status_event_1 ) then event_handler_1();<br />

3 if( status_event_2 ) then event_handler_2();<br />

4 //...<br />

5 if( status_event_n ) then event_handler_n();<br />

6 }<br />

<strong>Programming</strong> frameworks supporting a control loop as their main approach<br />

to reactive programming have several benefits. Firstly, they require no concurrency<br />

and event-handling support from an underlying runtime system, such as<br />

context switches or event queuing and dispatching. The only support required<br />

are drivers that allow to check the status of a device in a non-blocking fashion.<br />

There<strong>for</strong>e, their runtime environments are very lean. Secondly, programmers<br />

have full control over how and when events are handled. The entire control<br />

flow of the program is exclusively managed by application programmers and<br />

can thus be highly optimized.<br />

On the other hand, the control-loop approach does not en<strong>for</strong>ce a particular programming<br />

style. Programmers carry the burden to implement event detection<br />

(i.e., polling operations), queuing and dispatching without being guided along a<br />

well approved and established path. This burden can be hard to master even <strong>for</strong><br />

expert programmers. Particularly when event detection and event handling are<br />

not clearly separated in a control loop, the model’s high flexibility can lead to<br />

code that is very unstructured and there<strong>for</strong>e hard to maintain and extend. Such<br />

code typically depends on timing assumptions and is non-portable. Also, programmers<br />

are responsible <strong>for</strong> implementing sleep modes to avoid busy-waiting<br />

when no events occur and need to be processed. Implementing sleep modes<br />

is particularly troublesome when event detection and handling are not clearly<br />

separated.<br />

3.2.3 Event-driven <strong>Programming</strong><br />

The event-driven programming model can be seen as a conceptualization of the<br />

control-loop approach. It does en<strong>for</strong>ce the separation of event detection and<br />

event dispatching by making the control loop an integral part of its runtime<br />

environment. The event-driven model is the most popular programming model<br />

<strong>for</strong> developing sensor-network applications today. Several programming frameworks<br />

based on this model exist, <strong>for</strong> example, our BTnode system software [21],<br />

but also the popular TinyOS and nesC [45], Contiki [37], SOS [52] and Maté [79].


3.2. <strong>Programming</strong> <strong>Model</strong>s 37<br />

The Basic <strong>Programming</strong> <strong>Model</strong><br />

The event-driven model is based on two main abstractions: events and actions.<br />

Events represent critical system conditions, such as expired timers, the reception<br />

of a radio message, or sensor readouts. Events are typically typed to distinguish<br />

different event classes and may contain parameters, <strong>for</strong> example, to carry associated<br />

event data. At runtime, the occurrence of an event can trigger the execution<br />

of a computational action. Typically, there is a one-to-one association between<br />

event types and actions. Then, an event-driven program consists of as many<br />

actions as there are event types (see pseudo code in Prog. 3.2).<br />

Actions are typically implemented as functions of a sequential programming<br />

language. Actions always run to completion without being interrupted by other<br />

actions. A so-called dispatcher manages the invocation of actions. Dispatchers<br />

often use an event queue to hold unprocessed events. The internal implementation<br />

of dispatchers typically follows the control-loop approach.<br />

Event-driven systems are typically implemented in a single flow of control.<br />

(Indeed, in sensor networks they are often deployed just to avoid the overhead<br />

of concurrent systems.) In order not to monopolize the CPU <strong>for</strong> any significant<br />

time and thus allow other parts of the system to progress, actions need to<br />

be non-blocking. Actions are expected to terminate in bounded time—typically<br />

less than a few milliseconds, depending on the application’s real-time requirements<br />

and the system’s event-queue size. There<strong>for</strong>e, at any point in the control<br />

flow where an operation needs to wait <strong>for</strong> some event to occur (e.g., a reply<br />

message or acknowledgment in a network protocol), the operation must be split<br />

into two parts: a non-blocking operation request and an asynchronous completion<br />

event. The completion event then triggers another action, which continues<br />

the operation.<br />

Event-driven programs consist of actions, each of which is associated to an<br />

event and which is invoked in response to the occurrence of that event. To<br />

specify a program, programmers implement actions (typically as functions of<br />

a sequential language). At system start time, control is passed to the dispatcher.<br />

The control then remains with the system’s dispatcher and is only passed to<br />

application-defined actions upon the occurrence of events. After the execution<br />

of an action, control returns to the dispatcher again. There<strong>for</strong>e, from the programmer’s<br />

point of view, applications consist of a number of distinct program<br />

fragments (i.e., the actions). These fragments are not executed in a given sequential<br />

order, as functions of procedural languages would. Rather, these actions are<br />

invoked in the order in which their associated events occur.<br />

Discussion<br />

The main strength of the event-based model is the en<strong>for</strong>cement of a well defined<br />

and intuitive programming style which incurs only little overhead. The<br />

event model is simple, yet nicely captures the reactive nature of sensor-network<br />

applications. It en<strong>for</strong>ces the by sensor-network experts commonly promoted<br />

programming pattern, in which short computations should only be triggered by<br />

events. Compared to the control-loop approach, programs in the event-driven<br />

model are clearly structured into a few well-defined actions. Furthermore, programmers<br />

must not concern themselves with sleep modes, as they are typically


38 Chapter 3. <strong>Programming</strong> and Runtime Environments<br />

Program 3.2: The general structure of an event-driven program.<br />

1 void main() {<br />

2 // initialize dispatcher, runs <strong>for</strong>ever<br />

3 start_dispatcher();<br />

4 // not reached<br />

5 }<br />

6 void event_handler_1( EventData data ) { // associated with event_1<br />

7 // initialize local variables<br />

8 process( data ); // process event data<br />

9 }<br />

10 void event_handler_2() { ... } // associated with event_2<br />

11 ...<br />

provided as basic service of the system software’s dispatcher.<br />

Supporting events in the underlying system software incurs only very little<br />

overhead. The system software must only provide basic services <strong>for</strong> event detection,<br />

queuing, and dispatching. It has been shown in several prototypes that<br />

these elements can be implemented very resource efficiently in software (see, <strong>for</strong><br />

example, [37, 52, 59, 73]). To further reduce resource and energy consumption,<br />

much of the model’s runtime system can be implemented in hardware, as has<br />

been shown in the SNAP processor architecture [39, 66]. Event-driven application<br />

programs can thus run in very lightweight execution environments.<br />

As a consequence, in the context of sensor networks, event-based systems are<br />

very popular with several programming frameworks in existence. Today, these<br />

frameworks are supported by a large user base, most notably TinyOS. Hence,<br />

many programmers are used to think in terms of events and actions and find it<br />

natural to structure their programs according to this paradigm.<br />

3.2.4 Multi-Threaded <strong>Programming</strong><br />

The multi-tasking model embodies another view on application programming.<br />

The multi-threading programming model focuses on describing application programs<br />

as a set of concurrent program fragments (called threads), each of which<br />

has its own context and control flow. Each thread executes a sequential program,<br />

which may be interrupted by other threads. Since wireless sensor nodes are typically<br />

single-processor systems, concurrency is only a descriptive facility. On the<br />

node’s processor, concurrent threads are scheduled sequentially. All threads of<br />

a program share resources, such as memory. Shared memory is typically used<br />

<strong>for</strong> inter-thread communication.<br />

The Basic <strong>Programming</strong> <strong>Model</strong><br />

The multi-threaded programming model is convenient <strong>for</strong> programming reactive<br />

sensor-node applications. Threads may block to await the occurrence of<br />

certain events, <strong>for</strong> example, the reception of a radio message. A thread waiting<br />

<strong>for</strong> a particular event becomes active again only after the occurrence of the


3.2. <strong>Programming</strong> <strong>Model</strong>s 39<br />

event. There<strong>for</strong>e, programmers can simply wait <strong>for</strong> the occurrence of particular<br />

events in a wait statement and handle the reaction to that event (in the same<br />

computational context) when the statement returns (see Prog. 3.3). To handle<br />

the different events that are of interest to an application, programmers typically<br />

use several threads, one per event or group of related events. While a thread is<br />

blocking, the operating system schedules other threads, which are not waiting.<br />

Programmers need not worry about the reactivity of the program when a<br />

thread blocks or when it per<strong>for</strong>ms long-running operations, as the rest of the application<br />

remains reactive and continues to run. For this reason, graphical user<br />

interfaces (GUIs) of PC applications are often implemented in their own thread.<br />

In wireless sensor networks, threads are typically used to specify the subsystems<br />

of a sensor node, such as network management, environmental monitoring, and<br />

monitoring the node’s local resources. <strong>Programming</strong> loosely coupled parts of an<br />

application as separate threads can increase the structure and modularity of the<br />

program code.<br />

In preemptive multi-threading systems, a thread of execution can be interrupted<br />

and the control transferred to another thread at any time. Multiple<br />

threads usually share data, thus requiring synchronization to manage their interaction.<br />

Synchronization between threads must ensure deterministic access<br />

to shared data, regardless how threads are actually scheduled, that is, how<br />

their threads of execution are interleaved. Proper synchronization prevents<br />

data inconsistencies when concurrent threads simultaneously modify and access<br />

shared data. Synchronization is often implemented using semaphores or<br />

by en<strong>for</strong>cing atomic execution (by disabling context switches).<br />

Program 3.3: The structure of multi-threaded sensor-node program.<br />

1 void main() {<br />

2 // initialization of two threads<br />

3 start( thread_1 );<br />

4 start( thread_2 );<br />

5 }<br />

6 void thread_1() {<br />

7 // initialization of local variables<br />

8 event_data_t ev;<br />

9 while( true ) { // do <strong>for</strong>ever<br />

10 wait<strong>for</strong>( EV_1, ev ); // wait <strong>for</strong> event of type EV_1<br />

11 process( ev.data ); // process event data<br />

12 wait<strong>for</strong>( EV_2, ev ); // wait <strong>for</strong> event of type EV_2<br />

13 process( ev.data ); // process event data<br />

14 // ...<br />

15 }<br />

16 }<br />

17 void thread_2() { ... }


40 Chapter 3. <strong>Programming</strong> and Runtime Environments<br />

System Requirements <strong>for</strong> Multi-Threading<br />

Multi-threading requires operating-system level support <strong>for</strong> switching between<br />

the contexts of different tasks at runtime. In a context switch, the currently running<br />

thread is suspended by the system software and another thread is selected<br />

<strong>for</strong> execution. Then the context of the suspended thread must be saved to memory,<br />

so that it can be restored later. The context contains the program state as<br />

well as the processor state, particularly any registers that the thread may be using<br />

(e.g., the program counter). The data-structure <strong>for</strong> holding the context of a<br />

thread is called a switchframe. The system software must then load the switchframe<br />

of the thread to run next and resume its execution.<br />

Multi-threading requires significant amounts of memory to store the switchframes<br />

of suspended threads. Also, in every context switch, data worth of two<br />

switchframes is copied, from the registers to data memory and vice versa. For<br />

slow and resource-constrained sensor nodes this means a significant investment<br />

in memory as well as CPU cycles [36, 59].<br />

For example, the Atmel ATmega 128 microcontroller, a microcontroller used in<br />

several COTS sensor-node designs (e.g., Berkeley Motes, BTnodes, and others),<br />

has 25 registers of 16 bit each. There<strong>for</strong>e each context switch requires to copy<br />

100 bytes of data only <strong>for</strong> saving and restoring the processor state. Experiments<br />

on this microcontroller in [36] confirm that the context switching overhead of<br />

multi-threaded programs is significant, particularly under high system load. A<br />

test application run under high load (i.e., under a duty cycle of about only 30%)<br />

on the multi-threaded Mantis operating system exhibited 6.9% less idle time<br />

compared to a functionally equivalent application on the event-driven TinyOS.<br />

(Be<strong>for</strong>e both operating systems had been ported to the same hardware plat<strong>for</strong>m).<br />

The reduction in idle time and the associated increase in power consumption are<br />

attributed to the switching overhead.<br />

Additionally, each thread has its own state, which is stored by the runtime<br />

stack. The runtime stack stores in<strong>for</strong>mation about the hierarchy of functions<br />

that are currently being executed. Particularly it contains the parameters of<br />

functions, their local variables, and their return addresses. The complete perthread<br />

stack memory must be allocated when the thread is created. It cannot<br />

be shared between concurrent threads. Because it is hard to analyze how much<br />

stack space a thread needs exactly, runtime per-thread stacks are generally overprovisioned.<br />

The consequence of under-dimensioned stacks are system crashes<br />

at runtime.<br />

The overhead of per-thread data-structures, that is, runtime stacks and switchframes,<br />

is often <strong>for</strong>gotten in evaluations of memory footprints of multi-threaded<br />

sensor-node operating systems. However, this inevitable memory overhead can<br />

consume large parts of the memory resources of constrained nodes (cf. [37]).<br />

Reliability, Debugging, and Modularity Issues<br />

Besides of its memory overhead, multi-threading has issues with reliability and<br />

modularity. Programmers are responsible <strong>for</strong> synchronizing threads manually<br />

using special synchronization primitives, which materializes as additional program<br />

code. Proper synchronization requires a comprehensive understanding<br />

of the data and timing dependencies of all interacting threads within a pro-


3.2. <strong>Programming</strong> <strong>Model</strong>s 41<br />

gram. Gaining this understanding becomes increasingly difficult in programs<br />

with many and complex dependencies, particularly when multiple programmers<br />

are involved.<br />

It is generally acknowledged that thread-synchronization is difficult, because<br />

threads may interact with each other in unpredictable ways [30, 94]. Common<br />

errors are data races and deadlocks. Data races occur because of missing thread<br />

synchronization. However, also the overuse of synchronization primitives can<br />

lead to errors, namely deadlocks, and execution delays. Particularly atomic sections<br />

in application programs can delay time critical operations within drivers<br />

and lead to data loss. Such errors may break the application program as well as<br />

even the most carefully crafted error-recovery mechanisms, rendering affected<br />

sensor nodes useless in the field. Choi et al. [30] have found that programmers<br />

that are new to multi-threading often overuse locking primitives.<br />

On top of being error-prone, multi-threaded programs are also very hard to<br />

debug. Often synchronization errors depend on the concrete timing characteristics<br />

of a program [30]. Local modifications to the program code can expose<br />

a previously undetected error in a different part of the code. In the case of an<br />

error typically the exact timing history cannot be reproduced to aid in debugging.<br />

These characteristics make tracking down and fixing errors painful and<br />

time consuming.<br />

Finally, multi-threading may also hinder program modularity. Threads typically<br />

communicate with shared data. Because of the synchronization required<br />

<strong>for</strong> the protection of shared data, interacting threads are no longer independent<br />

of each other. To reuse threads in a different program context or on a different<br />

hardware plat<strong>for</strong>m programmers must understand their internals, such as<br />

data dependencies and timings, and carefully integrate them into the new program<br />

context. There<strong>for</strong>e threads cannot be designed as independent modules<br />

and have no well defined interfaces.<br />

Discussion<br />

Though threads are a powerful programming abstraction (and particularly more<br />

powerful than the event/action abstraction of the event-driven model), its<br />

power is rarely needed. Because of its reliability and modularity issues, it has<br />

been suggested [94] that multi-threaded programming in general (i.e., even <strong>for</strong><br />

general purpose computing plat<strong>for</strong>ms) should only be used in the rare cases<br />

when event-driven programming does not suffice. Particularly <strong>for</strong> programs<br />

that have complex data dependencies, thread synchronization becomes too difficult<br />

to master <strong>for</strong> most programmers. On the contrary, due to the run-tocompletion<br />

semantics, synchronization is rarely an issue in event-driven programming.<br />

Additionally, the event-driven model incurs less overhead.<br />

As we will discuss in the next chapter, WSN applications typically have many<br />

and complex data dependencies throughout all parts of the program. Because<br />

of the severe reliability and modularity issues that are thus to be expected and<br />

the high resource overhead, we believe that the multi-threaded programming<br />

model is not suitable as the main programming model <strong>for</strong> particularly resourceconstrained<br />

sensor nodes. This belief concurs with the view of many sensornetwork<br />

system experts (cf. [59, 60] and others). Most notably the designers of


42 Chapter 3. <strong>Programming</strong> and Runtime Environments<br />

the Contiki programming framework [37], which combines the multi-threaded<br />

and event-driven programming model, suggest that threads should be used<br />

only as a programmer’s last resort.<br />

3.3 Memory Management<br />

In general-purpose programming frameworks there are two basic mechanisms<br />

to memory management, which are typically used both. Dynamic memory management<br />

provides programmers with a mechanism to allocate and release chunks<br />

of memory at any point in the program during runtime. In the absence of dynamic<br />

memory management (i.e., with static memory management), chunks of<br />

memory can only be allocated at compile time. These chunks can neither be released<br />

nor can their size be changed at runtime. Dynamic memory management<br />

does not depend on a particular programming or process model (as discussed<br />

in the next section)—it can be used in combination with any model.<br />

The second mechanism <strong>for</strong> the allocation of (typed) data is automatic variables<br />

(typically also called local variables). Automatic variables are tied to the scope of<br />

a function in sequential languages and are typically allocated on the stack. When<br />

the function ends, automatic variables are automatically released (i.e., without<br />

the manual intervention of a programmer). Automatic variables are considered<br />

an integral part of sequential (i.e., procedural) programming languages. They<br />

are provided by all sensor-node programming frameworks based on procedural<br />

languages known to us. On the other hand, dynamic memory management has<br />

several implementation and reliability issues, which has lead to its exclusion in<br />

many sensor-node software designs. In this section we will briefly discuss these<br />

issues.<br />

3.3.1 Resource Issues<br />

Today, dynamic memory management (as provided, <strong>for</strong> example, by the traditional<br />

C-language API malloc() and free()), is taken <strong>for</strong> granted by most<br />

programmers. However, dynamic memory management is a major operatingsystem<br />

service and requires thoughtful implementation. The memory allocation<br />

algorithm must minimize the fragmentation of allocated memory chunks over<br />

time but must also minimize the computational ef<strong>for</strong>t <strong>for</strong> maintaining the freememory<br />

list. Implementations of memory-allocation algorithms can constitute a<br />

significant amount of the code of a sensor node’s system software. Also, the data<br />

structures <strong>for</strong> managing dynamic memory can consume significant amounts of<br />

memory by themselves, particularly if arbitrary allocation sizes are supported.<br />

3.3.2 Reliability Concerns<br />

In addition to the high cost of implementing dynamic memory management,<br />

there are several reliability concerns. Typically coupled with virtual memory<br />

and memory protection, dynamic memory management in traditional systems<br />

relies on the support of a dedicated piece of hardware, the Memory Management<br />

Unit (MMU). Since resource-constrained sensor nodes do not have a MMU, they


3.3. Memory Management 43<br />

do not support memory protection. Thus the private data of user applications<br />

and the operating system cannot be protected mutually from erroneous write<br />

access.<br />

Besides a general susceptibility to memory leaks, null-pointer exceptions, and<br />

dangling pointers, there is an increased concern to run out of memory. Due to<br />

the lack of cheap, stable, fast, and power efficient secondary storage, non of the<br />

sensor-node operating systems known to us provide virtual memory. Instead,<br />

sensor-node programs have to rely on the limited physical memory available on<br />

the node.<br />

An application’s memory requirements need to be carefully crafted to under<br />

no circumstances exceed the available memory (or to provide out-of-memory<br />

error handlers with every memory allocation). However, crafting an application’s<br />

memory requirements is considered impractical or at least very hard <strong>for</strong><br />

systems supporting concurrency and <strong>for</strong> large applications that grow over time<br />

and which involve multiple developers. Concurrent programs and threads compete<br />

<strong>for</strong> the available memory. It is hard to reason about the maximum memory<br />

requirements of a program as that would require to test every single control path<br />

through the application <strong>for</strong> memory allocations.<br />

While memory leaks, null-pointer exceptions, and dangling pointers could be<br />

possibly debugged with the help of specialized development tools, it should be<br />

noted that there are no practical techniques to reason about the entire memory<br />

requirements (including stack size and dynamic memory) of an application. In<br />

order to avoid out-of-memory errors, either the physical memory installed on<br />

a node must be greatly over-provisioned (in terms of the program’s expected<br />

maximum memory usage) or the developer relies entirely on static memory allocation<br />

(plus other safety measures to prevent unlimited stack growth, like prohibiting<br />

recursion).<br />

3.3.3 Dynamic Memory Management <strong>for</strong> <strong>Sensor</strong> Nodes<br />

Many sensor-node programmers have been hesitant to use dynamic memory<br />

due to the mentioned implementation and reliability issues [48]. There<strong>for</strong>e,<br />

many operating systems <strong>for</strong> sensor nodes do not provide support <strong>for</strong> dynamic<br />

memory management, <strong>for</strong> example, TinyOS [45], the BTnode system software<br />

[21], and BTnut [123]. Currently MANTIS [8] does not provide dynamic<br />

memory management but it is scheduled <strong>for</strong> inclusion in future versions.<br />

Other operating systems <strong>for</strong> sensor nodes, like SOS [52], Impala [81], and<br />

SNACK [48], provide dynamic memory management, but, in order to avoid<br />

fragmentation of the heap, use fixed block sizes. Fixed-size memory allocation<br />

typically results in the allocation of blocks that are larger than needed and there<strong>for</strong>e<br />

wastes a precious resource. In SNACK [48] only buffers <strong>for</strong> network messages<br />

are dynamically allocated from a managed buffer pool. Variables have to<br />

by allocated statically.


44 Chapter 3. <strong>Programming</strong> and Runtime Environments<br />

3.4 Process <strong>Model</strong>s<br />

Besides a basic programming model, the system software also provides other<br />

features to support normal operation, such as process management. Processes<br />

can be thought of as programs in execution. Two important features found in<br />

most traditional system software related to process management are the ability<br />

to run several processes concurrently and the ability to create processes dynamically.<br />

It has been argued that these features would also be useful <strong>for</strong> sensor<br />

networks, particularly in combination. For example, software maintenance and<br />

changing application needs may require updates to a node’s software at runtime<br />

[52], that is, after the node has been deployed and has started program<br />

execution. It may also be useful to start and run applications in parallel [25],<br />

<strong>for</strong> example, to run short-term tests or to per<strong>for</strong>m sensor recalibration without<br />

disrupting long-running monitoring applications.<br />

Dynamically loadable programs and process concurrency are independent of<br />

each other and require different support mechanisms in the system software.<br />

Here again, <strong>for</strong> reasons of resource-efficiency and reliability, system designers<br />

have to make design tradeoffs. In this section we will first present the runtime<br />

support required <strong>for</strong> each feature be<strong>for</strong>e analyzing them together.<br />

3.4.1 Overview<br />

Operating systems supporting a dynamic process model can load a program’s executable<br />

(e.g., from the network or from secondary storage) at runtime to create<br />

a new process. They can also terminate a process at runtime and remove its executable<br />

from the system’s memories. In contrast to this, in a static process model,<br />

the system has to be shut down and reconfigured be<strong>for</strong>e a new application can<br />

be run or removed from the system. Since physical contact with the node is often<br />

infeasible after deployment of the WSN, several sensor-node plat<strong>for</strong>ms provide<br />

a mechanism <strong>for</strong> delivering executables over-the-air through the wireless interface.<br />

A dynamic process model is most useful and there<strong>for</strong>e often combined<br />

with a concurrent process model, where several programs can run “at the same<br />

time” by sharing the processor in time-multiplex.<br />

3.4.2 Combinations of Processes <strong>Model</strong>s<br />

Modern general-purpose operating systems combine dynamic and concurrent<br />

process models. In contrast, several sensor-node plat<strong>for</strong>ms, like TinyOS, BTnut,<br />

and the BTnode system software, only provide a static, non-concurrent process<br />

model. All existing sensor-node plat<strong>for</strong>ms that do provide dynamic processes<br />

loading, however, also provide an environment <strong>for</strong> their concurrent execution.<br />

Dynamic and Concurrent Process <strong>Model</strong>s<br />

In a dynamic process model, individual processes can be created at runtime.<br />

To support dynamic loading of a process at runtime the program’s binary image<br />

must be loaded (e.g., from secondary storage or over the network) into the


3.4. Process <strong>Model</strong>s 45<br />

node’s different memories. Process creation involves loading the program’s executable<br />

code (known as the text section) into the node’s program memory, while<br />

statically allocated and initialized data (known as the data section, which contains,<br />

<strong>for</strong> example, global and static variables) are copied to data memory. At<br />

compile time the exact memory locations where the program will reside after<br />

loading is unknown. There<strong>for</strong>e, the compiler generates so-called relocatable<br />

code, where variables and functions are addressed by relative memory locations<br />

only. The system software then replaces the relative memory locations<br />

with absolute addresses when the program is loaded at runtime. This process<br />

is called relocation. In a combined, dynamic and concurrent program model, the<br />

newly created process is added to the list of currently running processes. The<br />

system software often is a separate process). For example, system services in<br />

Contiki [37] are implemented as processes that can be replaced at runtime.<br />

Static and Non-Concurrent Process <strong>Model</strong><br />

On the other hand, in a static process model in combination with a non-concurrent<br />

process model only a single program can be installed and running at any time.<br />

In such systems, the application code is linked to the system software at compile<br />

time, resulting in a monolithic executable image. The executable image is<br />

then uploaded to the memories of the node, effectively overwriting any previous<br />

software (application code as well as system-software code). A relocation<br />

step is not required.<br />

To be able to change the node’s software despite a static process model, several<br />

runtime environments allow to reload the entire monolithic system image.<br />

To do so, the currently running software stops its normal operation to receive<br />

the new software image from the network and then saves it to data memory.<br />

Then it reboots the processor in a special bootloader mode. The bootloader finally<br />

copies the image from data memory to program memory and reboots the<br />

processor again in normal mode to start the newly installed image.<br />

Other Combinations<br />

Other combinations (concurrent but static; dynamic but non-concurrent) are also<br />

possible, but have not seen much use in sensor-node operating systems. Only<br />

the Maté virtual machine [79] allows to run a single, non-concurrent process,<br />

which can be dynamically replaced during runtime.<br />

3.4.3 Over-the-Air Reprogramming<br />

To allow software updates of nodes deployed in the field, several systems implement<br />

over-the-air reprogramming, where a new program image can be downloaded<br />

over the wireless interface. Over-the-air reprogramming should not be<br />

confused with a dynamic process model. Over-the-air reprogramming merely<br />

denotes the systems capability to receive a program image over the air and to<br />

initiate its loading. The actual loading of the executable image to the system’s<br />

memories then still requires one of the loading procedure as described above.


46 Chapter 3. <strong>Programming</strong> and Runtime Environments<br />

Actual implementations of over-the-air programming often require the cooperation<br />

of user-written application code. Over-the-air reprogramming is often<br />

provided as a substitute <strong>for</strong> dynamic process loading by several resourceconstrained<br />

sensor-node architectures. It has been deployed, <strong>for</strong> example, in<br />

BTnode system software [21], BTnut [123], TinyOS [45]), Maté [79], and others.<br />

A detailed survey of software update management techniques can be found<br />

in [51].<br />

3.4.4 Process Concurrency<br />

In a concurrent process model, several processes can run at the same time. Since<br />

wireless sensor nodes are typically single-processor systems, concurrent processes<br />

need to be interleaved, with the processor multiplexed among them. Processor<br />

multiplexing is called scheduling and is typically per<strong>for</strong>med by the system<br />

software as it is considered a fundamental system service. The points in<br />

the programs where switching is best per<strong>for</strong>med depends on the programs’ internal<br />

structure. There<strong>for</strong>e, scheduling mechanisms are closely related to programming<br />

models and their corresponding execution environments. For sensor<br />

nodes two basic scheduling strategies exist. Architectures with an event-driven<br />

execution environment typically provide a concurrency model that is also based<br />

on events. Systems with a multi-threaded execution environment provide a concurrency<br />

model based on context switches.<br />

Context-Switching Processes<br />

The design of process concurrency based on context switches <strong>for</strong> sensor nodes<br />

can be best understood by comparing it to designs in general-purpose operating<br />

systems.<br />

Multi-tasking in general-purpose operating systems. Modern general-purpose<br />

operating systems have two levels of concurrency—the first level among processes,<br />

and the second level among the threads within a process. Both levels<br />

have their own scheduling mechanism, which are both based on context<br />

switches. Context-switching processes is referred to as multi-tasking. (The terms<br />

task and process are often used synonymously.)<br />

Multi-threading and multi-tasking are similar in many respects but differ in<br />

the way they share resources. They both provide concurrency, the <strong>for</strong>mer among<br />

the threads of a process, the latter among the processes running on a system.<br />

Just like threads, tasks have their own sequential control flow and their own<br />

context. And just like multi-threading, multi-tasking requires context switches<br />

and individual stacks (one per task) to store context in<strong>for</strong>mation. There<strong>for</strong>e, the<br />

system support required <strong>for</strong> multi-tasking is also very similar to that of multithreading,<br />

which we discussed previously in Sect. 3.2.4 on page 38.<br />

The main difference to threads is that tasks typically do not share resources,<br />

such as files handles and network connections. In particular, tasks have their<br />

own memory regions that are protected against accidental access from other<br />

tasks. This is desirable to protect correct programs from buggy ones. To provide<br />

communication and synchronization amongst processes despite those protec-


3.4. Process <strong>Model</strong>s 47<br />

tive measures, special mechanisms <strong>for</strong> inter-process communication exist, like<br />

shared memory, message passing, and semaphores.<br />

The main objectives of process concurrency in general-purpose operating systems<br />

is to optimize processor utilization and usability (cf. [108]). While one<br />

process is waiting <strong>for</strong> an input or output operation to commence (e.g., writing<br />

a file to secondary storage or waiting <strong>for</strong> user input), another process can continue<br />

its execution. There<strong>for</strong>e some process is always running. From a user perspective,<br />

process concurrency can increase the systems usefulness and reactivity.<br />

Running several programs in parallel allows to share the processing power of a<br />

single computer among multiple users. To each of the users it appears as if they<br />

had their own (albeit slower) computer exclusively to them. Also, a single user<br />

running several applications can switch back and <strong>for</strong>th between them without<br />

having to terminate them.<br />

Context-switching sensor-node processes. In order to avoid the resource overhead<br />

induced by two layers of scheduling, context-switsched sensor-node operating<br />

systems typically only provide a single level of scheduling on the basis of<br />

threads. That is, threads are the only scheduled entity. This does not mean,<br />

however, that multi-threaded sensor-node operating systems cannot provide<br />

process concurrency. If they do, the threads of distinct processes are contextswitched,<br />

thereby also switching processes. Processes then consist of a collections<br />

of threads, which are indistinguishable from the threads of other processes.<br />

The notion of a process is entirely logical, denoting all threads belonging to a<br />

program. In simple implementations a fine-grained control mechanism over the<br />

CPU time assigned to processes is missing; a process with more threads may receive<br />

more CPU time. To dynamically create a new process, the binary image of<br />

the program (consisting of a collection of threads) is loaded and all of its threads<br />

are instantiated.<br />

Threads or tasks? In the face of a single level of context switching, the question<br />

my arise, why we consider the context-switched entities of sensor-node<br />

system software to be threads rather then processes. This seems to be a rather<br />

arbitrary, if not an unusual view, because in the history of modern operatingsystem<br />

development, support <strong>for</strong> concurrent processes (e.g., multi-tasking) was<br />

introduced well be<strong>for</strong>e concurrency within individual processes, as provided by<br />

multi-threading (cf. [27]). In fact, multi-threading was long missing in generalpurpose<br />

operating systems.<br />

Though threads and tasks are similar in several respects, they have been developed<br />

<strong>for</strong> different reasons and serve different objectives. While multi-tasking<br />

aims more at the user of the computer system, multi-threading is mainly an abstraction<br />

to aid programmers. In sensor networks, concurrency is mainly considered<br />

a tool <strong>for</strong> specifying reactive programs while supporting multiple processes<br />

or even multiple users is considered less important. Also, the context-switched<br />

entities in wireless sensor networks have much more in common with threads<br />

than processes, because of the way they share resources. Most of the measures<br />

to protect distinct processes found in traditional multi-tasking systems are not<br />

(and cannot be) implemented in sensor networks. Because of these reasons, the<br />

term task has become widely accepted in sensor-network literature to denote<br />

context-switched entities.


48 Chapter 3. <strong>Programming</strong> and Runtime Environments<br />

Event-based Process Concurrency<br />

In programming frameworks based on the event-driven programming model,<br />

programs are composed of several actions. A property of the event-driven programming<br />

model is that the actions of a program are scheduled strictly in the order<br />

of the occurrence of their associated events. Scheduling mechanisms <strong>for</strong> the<br />

concurrent execution of event-driven programs must not violate this property.<br />

A possible implementation would be to context-switch concurrent event-driven<br />

processes. Then, all the actions of a single program would still have run-tocompletion<br />

semantics, while actions of separate processes could interrupt each<br />

other. However, because of the associated context-switching overhead of such a<br />

approach, system designers prefer to also schedule the actions of separate processes<br />

sequentially. For each event, the system software invokes the associated<br />

action of every process. Actions then always run to completion. This approach<br />

to scheduling actions of multiple, concurrent processes is depicted in Fig. 3.2.<br />

Application 1<br />

Application 2<br />

Scheduler<br />

Event occurence<br />

Event queue<br />

Key:<br />

t_1<br />

{}<br />

event occurance<br />

ev1<br />

action_1_1<br />

ev2<br />

t_2<br />

action_2_1<br />

{ev1} {ev1, ev2}<br />

delayed event handling<br />

action_1_2<br />

t_1’ t_2’ [t]<br />

{ev2}<br />

action_2_2<br />

{}<br />

passing of control flow<br />

Figure 3.2: Scheduling concurrent, event-driven processes. For each event, the<br />

system software invokes the associated action of every process. Actions<br />

always run to completion; their execution order is arbitrary.<br />

In Fig. 3.2 there are two event-driven programs running concurrently. Both<br />

of them react to events ev1 and ev2 (in actx1 and actx2, respectively), where x<br />

denotes the application number. On the occurrence of ev1 the system software<br />

first invokes act11 (of application 1) then act21 (of application 2). At time t ′ 1 all<br />

actions triggered by ev1 have run to completion. Event ev2 occurs be<strong>for</strong>e t ′ 1 and<br />

is stored in the event queue. There<strong>for</strong>e, reactions to ev2 are delayed until time t ′ 1,<br />

when it is dequeued.<br />

Two questions become particularly apparent when studying Fig. 3.2. Firstly,<br />

in which order should the event handlers be invoked? And secondly, how long<br />

can the invocation of an action be delayed? The answer to the first question may<br />

be of interest when both programs modify a shared state, such as the state of<br />

system resources. Then, race conditions and possibly deadlocks may occur. Unless<br />

explicitly specified in the system documentation, application programmers


3.4. Process <strong>Model</strong>s 49<br />

can not rely on any particular order as it depends on the actual implementation<br />

of the event-scheduling mechanism. In actual systems, the execution order of<br />

actions of separate processes is typically arbitrary.<br />

The answer to the second question—how long can actions be delayed?— may<br />

be important when applications have real-time constraints. The answer depends<br />

on the execution times of actions from the different programs running concurrently<br />

and can there<strong>for</strong>e not be answered easily by any single application developer.<br />

From our experience with the event-driven model it is already hard<br />

to analyze the timing dependencies in a single application without appropriate<br />

real-time mechanisms. In a concurrent execution environment, where no single<br />

application programmer has the entire timing in<strong>for</strong>mation, timing estimates are<br />

even harder to get by. There<strong>for</strong>e, concurrent process models based on events can<br />

decrease the systems reliability. Since almost all application have at least some<br />

moderate real-time constraints, we believe that process concurrency should be<br />

avoided on event-driven sensor nodes.<br />

Note that a question similar to the second (i.e., how long can the invocation<br />

of an action be delayed?) also arises in multi-treading environments. For such<br />

environments the re<strong>for</strong>mulated question is: how much slower can the program<br />

become with multiple threads running concurrently. In multi-treading environments<br />

the execution delay of operations only depend on the own application<br />

code and the number of programs running concurrently, not the contents (i.e.,<br />

the code) of other programs. For example, in the simple round-robin scheduling,<br />

the CPU time is equally divided among threads. If n threads are running, each<br />

thread is allocated to the CPU <strong>for</strong> the length of a time slice be<strong>for</strong>e having to wait<br />

<strong>for</strong> another n − 1 time slices. There<strong>for</strong>e the execution speed is only 1/(n)-th of<br />

a system with only a single program executing. Actual implementations would<br />

be slower since context-switching consumes non-negligible time.<br />

3.4.5 Analysis of Process <strong>Model</strong>s<br />

Mainly because of potentially unresolvable resource conflicts there are several<br />

reliability issues with process concurrency in sensor networks, regardless if its<br />

implementation is based on context-switches or events.<br />

When appropriate mechanisms <strong>for</strong> inter-process protection are missing,<br />

which is the rule rather than the exception, a single deficient process can jeopardize<br />

the reliability of the entire system. Even without software bugs it is practically<br />

impossible to analyze in advance whether two concurrent processes will<br />

execute correctly. One problem is, that programs compete <strong>for</strong> resources in ways<br />

that cannot be anticipated by programmers. If one process requires access to<br />

exclusive resources, such as the radio or sensors, they may be locked by another<br />

process. Limited resources, such as memory and CPU time, may be unavailable<br />

in the required quantities. Identifying potential resource conflicts is<br />

very hard and fixing them with alternate control flows is practically infeasible.<br />

To make things worse, programmers of different applications typically cannot<br />

make arrangements on the timing of CPU intense computations and high resource<br />

usage. There<strong>for</strong>e there are typically innumerable ways how two processes<br />

can ruin each others assumptions, particularly in the face of slow and<br />

resource-constrained sensor nodes.


50 Chapter 3. <strong>Programming</strong> and Runtime Environments<br />

Particularly the memory subsystem poses serious threads to concurrent programming.<br />

<strong>Sensor</strong> nodes do not provide virtual memory and there<strong>for</strong>e applications<br />

have to make do with the limited physical memory provided on a node.<br />

We expect that most non-trivial sensor-node programs occupy most of the systems’<br />

physical memory. It is generally hard to reach a good estimate of even<br />

a single program’s memory requirements, particularly with dynamic memory<br />

allocation. Multiple competing programs certainly increase the uncertainty of<br />

such estimates. If processes require more than the available memory, system<br />

crashes or undefined behavior may result. As a consequence, the affected node<br />

or even an entire network region connected through that node may be unusable.<br />

In sensor networks, concurrency on the process level does not play the same<br />

role as in general-purpose systems. Its purpose in traditional operating systems<br />

to increase processor utilization is contradictory to the design philosophy of sensor<br />

networks. In fact, as we pointed out earlier, most program architectures<br />

strive to maximize idle times in order to save power. Also, it is not expected<br />

that sensor nodes are used by multiple users to the same degree as traditional<br />

computing systems.<br />

Though process concurrency may be a useful feature <strong>for</strong> sensor-network operators<br />

and users, we suggest its implementation only in sensor-node design<br />

with appropriate protection mechanism and resource provisions. In constrained<br />

nodes, however, the are severe reliability issues, which make its support prohibitive.<br />

3.5 Overview and Examples of <strong>State</strong>-of-the-Art<br />

Operating Systems<br />

In the previous sections we have discussed the principle programming and<br />

process models, as well as dynamic memory-management support of current<br />

sensor-node operating systems. The discussion represents the state-of-the-art of<br />

sensor-node programming and is based on current literature. The discussion<br />

focuses on resource-constrained sensor node plat<strong>for</strong>ms.<br />

Be<strong>for</strong>e presenting two concrete examples of event-driven operating systems in<br />

greater detail, we will first summarize the previously discussed state-of-the-art<br />

sensor-node operating systems in Tab. 3.1. For each of the eight discussed operating<br />

systems, the table lists the system’s programming language, the process<br />

and programming model, and the target-device features. The concurrency column<br />

under the heading process model denotes if the OS supports running multiple<br />

processes concurrently. The dynamic loading column under the same heading<br />

denotes if new processes can be loaded and started at runtime without also having<br />

to reload and restart the OS itself. Systems supporting dynamic loading of<br />

processes typically also support running multiple processes concurrently. The<br />

only exception is the Maté virtual machine, which supports dynamic loading of<br />

user applications but can only run one of them at a time.<br />

Actual operating systems often implement variations of the basic programming<br />

models and often differ significantly in the system services provided. We<br />

will now present two representatives of current event-based operating systems;<br />

our BTnode system software, and the system that is sometimes referred to as the


3.5. Overview and Examples of <strong>State</strong>-of-the-Art Operating Systems 51<br />

<strong>Programming</strong> Framework Process <strong>Model</strong> <strong>Programming</strong> <strong>Model</strong> Target Device<br />

Operating<br />

System<br />

TinyOS<br />

[45]<br />

Maté VM<br />

[79]<br />

BTnode<br />

[21]<br />

BTnut<br />

[123]<br />

SOS<br />

[52]<br />

Contiki<br />

[37]<br />

Impala<br />

[81]<br />

MANTIS<br />

[8]<br />

Progr.<br />

Language<br />

Concurrency<br />

Dynamic<br />

Loading<br />

Scheduling Dyn.<br />

Mem.<br />

processor core, clock, RAM,<br />

ROM, secondary storage<br />

nesC no no events no Berkeley Motes<br />

8-bit, 8 MHz, 4 Kb, 128 Kb, -<br />

Maté<br />

bytecode<br />

no yes events no Berkeley Motes<br />

8-bit, 8 MHz, 4 Kb, 128 Kb, -<br />

C no no events no a BTnode (ver. 1 and 2)<br />

8-bit, 8 MHz, 4 Kb, 128 Kb<br />

C no no threads b no a BTnode (ver. 3)<br />

8-bit, 8 MHz, 64 Kb, 128 Kb,<br />

192 Kb RAM<br />

C yes yes events yes c Berkeley Motes and others<br />

8-bit, 8 MHz, 4 Kb, 128 Kb, -<br />

C yes yes events and<br />

threads d<br />

n/a n/a n/a prioritized<br />

events<br />

no a ESB node [47]<br />

16-bit, 1 MHz, 2 Kb, 60 Kb, -<br />

yes c ZebraNet node<br />

16-bit, 8 Mhz, 2 Kb, 60 Kb,<br />

512 Kb Flash RAM<br />

C yes yes threads d no Mantis node<br />

8-bit, 8 MHz, 4 Kb, 128 Kb, -<br />

a Dynamic memory allocation is provided by the standard C library libc <strong>for</strong> Atmel microcontrollers<br />

but is neither used in the OS implementation nor recommended <strong>for</strong> application programming.<br />

b Cooperative multi-threading.<br />

c Fixed (i.e., predefined) block sizes only.<br />

d Preemptive multi-threading.<br />

Table 3.1: Current programming frameworks <strong>for</strong> resource-constrained sensor<br />

nodes. The frameworks’ runtime environment (as provided by the<br />

system software) supports different process models and programming<br />

models as discussed in sections 3.2 and 3.4. (n/a: feature not specified<br />

in the available literature.)<br />

de facto standard of sensor-node operating systems, TinyOS. We will discuss<br />

their features and their deviations to the basic model in more detail.<br />

3.5.1 The BTnode System Software<br />

The BTnode system software (see Fig. 3.3) is a lightweight OS written in C and<br />

assembly language that has been initially developed <strong>for</strong> the first version of the<br />

BTnode. The system provides an event-based programming model.<br />

It does not provide dynamic loading of processes and has a non-concurrent<br />

process model. Though dynamic memory allocation is available, the system<br />

software is only using static memory allocation to avoid reliability issues. Application<br />

programmers are also strongly discouraged to used dynamic memory<br />

allocation. The drivers, which are available <strong>for</strong> many hardware subsystems and<br />

extensions (e.g, the Bluetooth radio and several sensors subsystems), provide<br />

convenient, event-driven APIs <strong>for</strong> application development.<br />

The BTnode system is composed of four principal components (see Fig. 3.3):<br />

the sensor-node hardware, the drivers, the dispatcher, and an application (which


52 Chapter 3. <strong>Programming</strong> and Runtime Environments<br />

Application<br />

Drivers<br />

Hardware<br />

Action 1 Action 2 Action 3 ... Action n<br />

Bluetooth<br />

RTC UART<br />

I2C<br />

<strong>Sensor</strong><br />

Board<br />

Interrupts and register access Function call<br />

BTnode<br />

read(), write(), control()<br />

GPIO<br />

Analog<br />

<strong>Sensor</strong>s<br />

ADC<br />

register<br />

invoke<br />

insert<br />

(event queue)<br />

Dispatcher<br />

Application <strong>Programming</strong> Interface<br />

Figure 3.3: The BTnode system software and programming framework <strong>for</strong> WSN<br />

applications.<br />

is composed of one or multiple actions). The components have clearly defined<br />

interfaces through which they are accessed. A central component is the dispatcher.<br />

Drivers and Dispatcher<br />

The drivers have two interfaces, a lower interface to the hardware and an upper<br />

interface to the application code. Through the lower interface they receive and<br />

send IO data in a hardware-specific manner through interrupt service routines<br />

and hardware registers. Through their upper interface they provide a convenient<br />

user-programming API <strong>for</strong> controlling the hardware and IO. Drivers never<br />

call any application code directly. Instead, to notify the application of hardware<br />

state changes, they use the dispatcher’s interface to insert an event into its eventqueue.<br />

This dispatcher then invokes the appropriate application code (i.e., an<br />

action).<br />

In the BTnode system software there is a hierarchy of drivers. Low-level<br />

drivers interact with the hardware directly (such the real-time clock driver).<br />

Higher-level drivers only interact with the hardware indirectly and require other<br />

lower-level drivers. For example, the Bluetooth driver relies on the real-time<br />

clock and the UART driver.<br />

The drivers are designed with fixed buffer lengths that can be adjusted at<br />

compile time to meet the stringent memory requirements. Available drivers<br />

include nonvolatile memory, real-time clock, UART, I 2 C, general purpose IO,<br />

LEDs, power modes, and AD converter. The driver <strong>for</strong> the Bluetooth radio provides<br />

a subset of the networking functionality according to the Bluetooth specification.<br />

It accesses the Bluetooth module through an UART at a speed of up to<br />

230400 baud. Bluetooth link management is per<strong>for</strong>med on Bluetooth’s L2CAP<br />

layer. RFCOM, a serial port emulation, provides connectivity to computer terminals<br />

and consumer devices, such as cameras and mobile phones. Dial-up con-


3.5. Overview and Examples of <strong>State</strong>-of-the-Art Operating Systems 53<br />

nections to modem servers through a mobile GSM phone are easily established<br />

with a special-purpose function. All other GSM services (such as file sharing,<br />

phone book and calendar) can be utilized through lower-level interfaces.<br />

<strong>Programming</strong> <strong>Model</strong><br />

The system is geared towards the processing of (typically externally triggered)<br />

events, such as sensor readings or the reception of data packets on the Bluetooth<br />

radio. To this end, BTnode applications follow an event-based programming<br />

model, where the system schedules user-specified actions on the occurrence of<br />

events. A set of predefined event types is used by the drivers to indicate critical<br />

system conditions.<br />

Upon detecting such a critical a condition, a driver inserts the corresponding<br />

event into the FIFO event-queue, which is maintained by the dispatcher. While<br />

the event-queue is not empty, the dispatcher removes the first event from the<br />

queue and invokes its associated user-defined action. Programmers implement<br />

actions as C functions. In actions, the programmer can use the (non-blocking)<br />

driver APIs to access IO data buffered in the drivers or to control their behavior.<br />

Programmers can also define their own event types and can insert instances<br />

of those events into the event queue from actions (not shown in Fig. 3.3). The<br />

different types of events are internally represented by 8-bit integer values (see<br />

Prog. 3.4 <strong>for</strong> examples).<br />

Unlike several other event-based systems the association of events and actions<br />

is not fixed. Rather, applications can dynamically associate actions with event<br />

types. For this purpose, the dispatcher offerers a dedicated registration function,<br />

which takes the an event and an action as parameter (see btn_disp_ev_reg()<br />

in Prog. 3.4). Despite these dynamics, the association between events and actions<br />

typically remains relatively stable throughout the execution of a program.<br />

Often it is established only once during program initialization. After the dispatcher<br />

has been started, it accepts events from drivers and applications. It remains<br />

in control until an event is inserted into the queue. Then it invokes the<br />

corresponding action. Once the action is completed, the dispatcher checks <strong>for</strong><br />

the next unprocessed event and invokes the corresponding action. Actions have<br />

a predefined signature as defined in Prog. 3.4, lines 11-13.<br />

Actions have two arguments, called call data and the callback data. Both arguments<br />

are of an untyped field of fixed length (32 bit); they are passed to the<br />

action when it is invoked by the dispatcher. The callback data argument can be<br />

specified by programmers in the dispatcher’s registration function. The argument<br />

is stored in the dispatcher and passed to actions when they are invoked.<br />

That is, the callback data is event-type specific. The call data argument is invocation<br />

specific. For user-defined event types the call-data values can be passed<br />

when inserting individual event instance into the queue. For system specified<br />

event types the call-data argument has a type-specific meaning. Its value is set<br />

by the driver generating the event. The predefined event types and their associated<br />

call-data arguments are:<br />

• TIMEOUT_EV: a timeout has expired. The call-data parameter carries a<br />

timestamp to indicate when the timer was set to expire (as there may be<br />

scheduling delay).


54 Chapter 3. <strong>Programming</strong> and Runtime Environments<br />

Program 3.4: API of the dispatcher. Lines 1-4 show examples of predefined<br />

event definitions; in line 7-13 the signature of actions are defined;<br />

the dispatcher’s registration function is shown in lines 16-19; the<br />

insert function is shown in lines 21-24.<br />

1 #define UART0_RCV_EV 2 // data available <strong>for</strong> reading on UART0<br />

2 #define BT_CONNECTION_EV 8 // Bluetooth connection occurred<br />

3 #define BT_DISCONNECT_EV 9 // Bluetooth disconnect occurred<br />

4 #define BT_DATA_RCV_EV 10 // Bluetooth data packet received<br />

5 // ...<br />

6<br />

7 typedef uint32_t call_data_t;<br />

8 typedef uint32_t cb_data_t;<br />

9<br />

10 // signature of an action<br />

11 typedef void (*callback_t) (<br />

12 call_data_t call_data,<br />

13 cb_data_t cb_data );<br />

14<br />

15 // the dispatcher’s registration function<br />

16 void btn_disp_ev_reg(<br />

17 uint8_t ev, // event ‘ev’ triggers action ‘cb’<br />

18 callback_t cb, // with the parameter specified<br />

19 cb_data_t cb_data); // in ‘cb_data’<br />

20<br />

21 // the dispatcher’s insert function<br />

22 void btn_disp_put_event( // insert ‘ev’ to the event queue<br />

23 uint8_t ev, // ‘call_data’ is passed as argument<br />

24 call_data_t call_data ); // to the action invoked by this event<br />

• ADC_READY_EV: a data sample is available from the ADC converter and<br />

it is ready <strong>for</strong> the next conversion. The call-data carries the conversion<br />

value.<br />

• I2C_RCV_EV: data has been received on the system’s I 2 C-bus and is now<br />

available <strong>for</strong> reading from the incoming buffer. The number of bytes available<br />

<strong>for</strong> reading at event-generation time is passed as call-data parameter.<br />

• I2C_WRT_EV: the I 2 C-bus is ready <strong>for</strong> transmitting data. The call-data<br />

parameter carries the current size of the outgoing buffer.<br />

• UART_RCV_EV: data has been received on the system’s UART and is now<br />

available <strong>for</strong> reading from the incoming buffer. The number of bytes available<br />

<strong>for</strong> reading at the event-generation time is passed as call-data parameter.<br />

• UART_WRT_EV: the UART is ready <strong>for</strong> transmitting data. The call-data<br />

parameter carries the current size of the outgoing buffer.


3.5. Overview and Examples of <strong>State</strong>-of-the-Art Operating Systems 55<br />

• BT_CONNECTION_EV: a Bluetooth connection-establishment attempt<br />

has ended. The connections status (i.e., whether the attempt was successful<br />

or has failed, as well as the reason <strong>for</strong> its failure) and the connection<br />

handle are passed as call data.<br />

• BT_DATA_RCV_EV: a Bluetooth data packet has been received and is<br />

ready <strong>for</strong> reading. The index to the packet buffer is passed as call data.<br />

The code in Prog. 3.5 shows a typical BTnode program. The program waits until<br />

a Bluetooth connection is established remotely and then sends a locally sampled<br />

temperature value to the remote unit. During initialization (lines 5-11) the program<br />

registers the action conn_action() to be called on BT_CONNECTION_EV<br />

events (line 8) and sensor_action() to be called on BT_ADC_READY_EV<br />

events (line 10). It then passes control to the dispatcher (line 11), which enters<br />

sleep mode until events occur that need processing.<br />

Once a remote connection is established, the Bluetooth driver generates the<br />

BT_CONNECTION_EV event and thus conn_action() (line 14) is invoked. In<br />

conn_action() the program first extracts the connection identifier from the<br />

call-data argument and saves to a global variable <strong>for</strong> later use (line 16). Then it<br />

starts the conversion of the analogue temperature value (line 18). On its completion,<br />

the BT_ADC_READY_EV event is generated and sensor_action() is<br />

invoked. Now the temperature value is extracted from the call-data argument<br />

(line 24) and sent back to the initiator (line 24), using the previously saved connection<br />

identifier. The same sequence of actions repeats on the next connection<br />

establishment. Until then, the dispatcher enters sleep mode.<br />

Process <strong>Model</strong><br />

Like most operating systems <strong>for</strong> sensor nodes, the BTnode system software does<br />

not support a dynamic process model. Only a single application is present on<br />

the system at a time. At compile time, applications are linked to the system<br />

software, which comes as a library. The resulting executable is then uploaded<br />

to the BTnode’s Flash memory, effectively overwriting any previous application<br />

code. After uploading, the new application starts immediately.<br />

However, the BTnode system can also be reprogrammed over-the-air using<br />

Bluetooth. To do so, the application currently running on the system needs to<br />

receive the new executable, save it to SRAM, and then reboot the system in bootloader<br />

mode. The bootloader finally transfers the received executable to Flash<br />

memory and starts it. Over-the-air programming is largely automated; it is a<br />

function of the system software but needs to be triggered by the user application.<br />

Portability<br />

The whole system software is designed <strong>for</strong> portability and is available <strong>for</strong> different<br />

operation environments (x86 and iPAQ Linux, Cygwin, and Mac OS X)<br />

apart from the BTnode plat<strong>for</strong>m itself. These emulations simplifies application<br />

building and speed up debugging since developers can rely on the sophisticated<br />

debugging tools available on desktop systems. Also, the time <strong>for</strong> uploading the


56 Chapter 3. <strong>Programming</strong> and Runtime Environments<br />

Program 3.5: A simple BTnode program handling two events in two actions.<br />

The action conn_action() handles (remotely) established connections<br />

while sensor_action() handles (local) sensor values<br />

after they become available.<br />

1 #include <br />

2 static uint16_t connection_id = 0;<br />

3<br />

4 int main( int argc, char* argv[] ) {<br />

5 btn_system_init( argc, argv, /* ... */ );<br />

6 btn_bt_psm_add( 101 ); /* accept remote connections */<br />

7 // register conn_action() to be invoked when a connection is established<br />

8 btn_disp_ev_reg( BT_CONNECTION_EV, conn_action, 0 );<br />

9 // register sensor_action() to be invoked when conversion is done<br />

10 btn_disp_ev_reg( BT_ADC_READY_EV, sensor_action, 0 );<br />

11 btn_disp_run();<br />

12 return 0; /* not reached */<br />

13 }<br />

14 void conn_action( call_data_t call_data, cb_data_t cb_data ) {<br />

15 // remember connection id <strong>for</strong> reply<br />

16 connection_id = (uint16_t)(call_data & 0xFFFF);<br />

17 // read temperature value from ADC converter<br />

18 bt_adc_start();<br />

19 }<br />

20 void sensor_action( call_data_t call_data, cb_data_t cb_data ) {<br />

21 // extract the converted value from the call-data argument<br />

22 uint8_t temperature = (uint8_t)(call_data & 0xFF);<br />

23 // reply with a packet containing only the temperature value (1 byte)<br />

24 btn_bt_data_send( connection_id, &temperature, 1 );<br />

25 }<br />

sensor-node application to the embedded target can be saved <strong>for</strong> testing. Furthermore,<br />

various device plat<strong>for</strong>ms (such as PCs or iPAQs running Linux) can be<br />

seamlessly integrated into BTnode networks (e.g., to be used as cluster heads),<br />

reusing much of the software written <strong>for</strong> the actual BTnode. These devices can<br />

then make use of the resources of the larger host plat<strong>for</strong>ms, <strong>for</strong> example, <strong>for</strong><br />

interfacing with other wireless or wired networks, or <strong>for</strong> providing extended<br />

computation and storage services.<br />

3.5.2 TinyOS and NesC<br />

Another representative of an event-driven programming framework is the one<br />

provided by the TinyOS operating system and its incorporated programming<br />

language nesC [45, 59]. (Since operating system and programming language inherently<br />

belong together, we use the term TinyOS to denote both. Only where<br />

not clear from the context, we use TinyOS to denote the execution environment<br />

and operating system, and nesC to denote the programming framework and<br />

language.) TinyOS is often considered the de facto standard in WSN oper-


3.5. Overview and Examples of <strong>State</strong>-of-the-Art Operating Systems 57<br />

ating systems and programming frameworks today. Since its introduction in<br />

2000, TinyOS has gained significant impact, possibly because of the success in<br />

the commercialization of its standard hardware plat<strong>for</strong>m, the Berkeley Motes.<br />

The early availability of the complementary combination of the freely available<br />

and well-supported programming framework and sensor-node hardware<br />

has made experimentation accessible to researchers without embedded systems<br />

background and without capabilities to develop and manufacture their own<br />

hardware.<br />

TinyOS targets resource-constrained sensor nodes and has been implemented<br />

on the Berkeley Mote family as well as other sensor nodes, such as the<br />

BTnode [78]. Its goal is to provide standard sensor-node services in a modular<br />

and reusable fashion. TinyOS has a component-oriented architecture based<br />

on an event-driven kernel. System services (such as multi-hop routing, sensor<br />

access, and sensor aggregation) are implemented as a set of components with<br />

well-defined interfaces. Application programs are also implemented as a set<br />

of components. TinyOS components are programmed in a custom C dialect<br />

(called nesC), requiring a special compiler <strong>for</strong> development. A simple declarative<br />

language is used to connect these components in order to construct more<br />

complex components or entire applications. The designers of TinyOS consider it<br />

a static language, as it supports neither dynamic-memory allocation, nor process<br />

concurrency or dynamic loading. (It does, however, provide static over-the-air<br />

reprogramming).<br />

TinyOS has an event-driven programming model. However, it has two levels<br />

of scheduling, rather than just one, as all other event-driven programming<br />

frameworks <strong>for</strong> sensor nodes. The scheduled entities are called tasks and events.<br />

This terminology is highly confusing: TinyOS tasks have no similarities to tasks<br />

in multi-tasking systems. Rather, a TinyOS task is what in event-driven systems<br />

is commonly known as an action or event handler. TinyOS tasks are scheduled<br />

sequentially by the scheduler and run to completion only with respect to other<br />

TinyOS tasks. They may be interrupted, however, by TinyOS events.<br />

A TinyOS event is not what is commonly considered an event in the eventdriven<br />

systems (i.e., a condition triggering an action). Rather, it is a timecritical<br />

computational function that may interrupt the regular control flow<br />

within TinyOS tasks as well as other TinyOS events. TinyOS events have been<br />

introduced so that a small amount of processing associated with hardware interrupts<br />

can be per<strong>for</strong>med as fast as possible, without scheduling overhead but<br />

instead interrupting long-running tasks. TinyOS events are required to complete<br />

as fast as possible, in order not to delay any other time-critical TinyOS events.<br />

As such, TinyOS events are programming abstractions <strong>for</strong> interrupt service routines.<br />

Apart from being triggered by hardware interrupts, TinyOS events can<br />

also be invoked by regular code. Then they behave like regular functions.<br />

Besides, TinyOS tasks and TinyOS events, TinyOS has a third programmatic<br />

entity, called commands. TinyOS commands resemble functions of sequential<br />

programming languages but have certain restrictions (e.g., they may not invoke<br />

TinyOS events).<br />

TinyOS applications can have a graph-like structure of components but are<br />

often hierarchical. Each component has a well-defined interface that can be used<br />

to interconnect multiple components to construct more complex components


58 Chapter 3. <strong>Programming</strong> and Runtime Environments<br />

or to <strong>for</strong>m an application. Each component interface defines the tasks, events,<br />

and commands it exports <strong>for</strong> use by other components. Each component has a<br />

component frame, which constitutes the component’s context, in which all tasks,<br />

events, and commands execute and which stores its state. Components may<br />

be considered objects of a static object-oriented language. Components always<br />

exist <strong>for</strong> the entire duration of a process and cannot be instantiated or deleted<br />

at runtime. The variables within a component frame are static (i.e., they have<br />

global lifetime) and have component scope (i.e., are visible only from with in<br />

the component). However, multiple static instances of a component may exist as<br />

parameterized interfaces. Parameterized interfaces are statically declared as array<br />

of components and are accessed by index or name. The elements of a component<br />

array provide identical interfaces but have individual states. They are used to<br />

model identical resources, such as individual channels of a multi-channel ADC<br />

or individual network buffers in an array of buffers.<br />

TinyOS is inherently event-driven and thus it shares the benefits of eventdriven<br />

systems. As an event-driven system it has only a single context in which<br />

all program code executes. There<strong>for</strong>e the implementation of TinyOS avoids<br />

context-switching overhead. All components share a single runtime stack while<br />

component frames are statically allocated, like global variables. As a deviation<br />

from the basic event-driven model, TinyOS has a two-level scheduling hierarchy<br />

which allows programmers to program interrupt service routines similar to<br />

event handlers using the TinyOS event abstraction. In most other event-driven<br />

systems, interrupts are typically hidden from the programmer and are handled<br />

within the drivers. IO data is typically buffered in the drivers and asynchronous<br />

events are used to communicate their availability to user code. In TinyOS, on<br />

the contrary, interrupts are not hidden from the programmer. This has been a<br />

conscious design decision. It allows to implement time critical operations within<br />

user code and provides greater flexibility <strong>for</strong> IO-data handling.<br />

However, this approach has a severe drawback. TinyOS events may interrupt<br />

tasks and other events at any time. Thus TinyOS requires programmers to explicitly<br />

synchronize access to variables that are shared within tasks and events<br />

(or within multiple events) in order to avoid data races. TinyOS has many of<br />

the synchronization issues of preemptive multi-tasking, which we consider one<br />

of the main drawback of TinyOS. In oder to support the programmer with synchronization,<br />

more recent versions of nesC have an atomic statement to en<strong>for</strong>ce<br />

atomic operation by disabling interrupt handling. Also, the nesC compiler has<br />

some logic to warn about potential data races. However, it cannot detect all<br />

race conditions and produces a high number of false positives. A code analysis<br />

of TinyOS (see [45]) and its applications has revealed that the code had many<br />

data races be<strong>for</strong>e race detection was implemented. In one example with race<br />

detection in place, 156 variables were found to have (possibly multiple) race<br />

conditions, 53 of which were false positives (i.e., about 30%).<br />

The high number of actual race conditions in the TinyOS show that interruptible<br />

code is very hard to understand and manage and thus highly error prone.<br />

Even though the compiler in the recent version now warns about potential data<br />

races, it does not provide hints on how to resolve the conflicts. Conflict resolution<br />

is still left to programmers. The high rate of false positives (about 30%) may<br />

leave programmers in doubt whether they deal with an actual conflict or a false


3.6. Summary 59<br />

positive. Chances are that actual races remain unfixed because they are taken<br />

<strong>for</strong> false alerts.<br />

Also, as [30] has shown, programmers who are insecure with concurrent programming<br />

tend to overuse atomic sections in the hope to avoid conflicts. However,<br />

atomic sections (in user-written TinyOS tasks) delay the execution of interrupts<br />

and thus TinyOS events. As a consequence, user code affects–and may<br />

break!–the system’s timing assumptions. Allowing atomic sections in user-code<br />

is contrary to the initial goal of being able to handle interrupts without scheduling<br />

delay. From our experience of implementing drivers <strong>for</strong> the BTnode system<br />

software we know that interrupt timing is highly delicate and should not be left<br />

to (possibly inexperienced) programmers but to expert system architects only.<br />

3.6 Summary<br />

In this chapter we have presented programming and runtime environments of<br />

sensor nodes. We have presented the three most important requirements <strong>for</strong><br />

sensor-node system software: resource efficiency, reliability, and programming<br />

support <strong>for</strong> reactivity. We have also presented the three main programming<br />

models provided by state-of-the-art system software to support application programming.<br />

And we have presented common process models of sensor-node<br />

system software, which support normal operation of sensor nodes, that is, once<br />

they are deployed. Finally, we have presented selected state-of-the-art runtime<br />

environments <strong>for</strong> sensor nodes.<br />

We have concluded that the control-loop programming model is overly simple<br />

and does not provide enough support even <strong>for</strong> experienced programmers. On<br />

the other hand, multi-threading is a powerful programming model. However,<br />

it has two main drawbacks. Firstly, multi-threading requires extensive system<br />

support <strong>for</strong> context switches. The per-thread data-structures (i.e., runtime stacks<br />

and switchframes), already consume large parts of a node’s memory resources.<br />

Also, copying large amounts of context in<strong>for</strong>mation requires many CPU cycles.<br />

Other authors be<strong>for</strong>e us have reached at the same conclusions (cf. [36, 37, 59, 60]).<br />

Also, multi-threading is considered too hard <strong>for</strong> most programmers because of<br />

the extensive synchronization required <strong>for</strong> accessing data shared among multiple<br />

threads. In [94] Ousterhout claims that multi-threading should be used<br />

only when the concurrent program parts are mostly independent of each other<br />

and thus require little synchronization. In sensor-node programming, however,<br />

threads are mainly used to specify the reactions to events that influence a common<br />

program state.<br />

To provide a programming model without the need <strong>for</strong> processor sharing, perthread<br />

stacks, and locking mechanisms, the event-driven programming model<br />

has been proposed <strong>for</strong> sensor-node programming. We and other authors have<br />

shown (both analytically and with prototype systems) that the event-driven programming<br />

model requires little runtime support by the underlying system software<br />

and can thus run on even the most constrained nodes. Yet it promises to<br />

be an intuitive model <strong>for</strong> the specification of sensor-node applications as it virtually<br />

embodies the event-action programming philosophy of wireless sensor<br />

networks.


60 Chapter 3. <strong>Programming</strong> and Runtime Environments<br />

Both of these models have their advantages and drawbacks, their followers<br />

and adversaries. In the context of sensor networks there is a bias towards eventbased<br />

systems, mainly due to the existence of popular event-based programming<br />

toolkits such as TinyOS and nesC, which have a large user base. Hence,<br />

many sensor-network programmers are already used to think in terms of events<br />

and actions and find it natural to structure their programs according to this<br />

paradigm.<br />

In the next chapter we will take a closer look at the event-driven model. We will<br />

analyze its expressiveness and resource requirements based on our own experience<br />

as well as on current literature. We will show that the event-driven model<br />

is also not without problems. There are also issues with reliability, program<br />

structure, and, ironically, with the memory efficiency of application programs<br />

(as opposed to operating systems, as is case with operating systems supporting<br />

multi-threading). We will show that these issues arise because the event-driven<br />

programming model is lacking a mechanism to clearly specify and memoryefficiently<br />

store temporary program states. But we will also sketch an approach<br />

to solve these problems. This approach will become the foundation of our OSM<br />

programming model and execution environment which we will present in chapter<br />

5.


4 Event-driven <strong>Programming</strong> in<br />

Practice<br />

The event-driven model is the favorite programming model among many<br />

sensor-network application programmers. In the last chapter we have shown<br />

several potential reasons <strong>for</strong> this preference: the model’s event-action paradigm<br />

is intuitive and captures the reactive nature of sensor-node programs well,<br />

event-driven system software requires little of the node’s constrained resources<br />

<strong>for</strong> its implementation, thus leaving most resources available <strong>for</strong> application<br />

programming, and finally, the early availability of the well-maintained eventdriven<br />

TinyOS plat<strong>for</strong>m combined with a commercial sensor-node, the Berkeley<br />

Motes.<br />

We have gained extensive experiences with the event-driven programming<br />

model ourselves. We have designed and implemented the BTnode system software,<br />

an event-driven runtime environment and programming framework <strong>for</strong><br />

BTnodes (as presented in Sect. 3.5.1). Some of the system’s more sophisticated<br />

drivers, such as the Bluetooth radio driver, are based on the event-driven<br />

model [21]. Also, we have developed many applications based on the eventdriven<br />

model [21], mainly in the Smart-Its [62] and Terminodes [65] research<br />

projects.<br />

However, in working with the BTnode system we have found two issues with<br />

the event-driven programming model. Firstly, the model is hard to manage, particularly<br />

as application complexity grows. Dunkles et al. [37] arrive at the same<br />

conclusion and [79] reports that programming TinyOS is “somewhat tricky” because<br />

of its event-driven programming model. And secondly, the event-driven<br />

programming imposes a programming style that results in memory inefficient<br />

application programs. Then, of course, two questions arise: What are the reasons<br />

<strong>for</strong> the model’s drawbacks? And: Is there a fix? In this chapter we will give<br />

an answer to the first question and we will outline an answer <strong>for</strong> the second.<br />

The baseline <strong>for</strong> both issues is that the event-driven model, though easy and<br />

intuitive, does not describe typical sensor-node applications very well. The<br />

model is sufficient <strong>for</strong> small sensor-network demonstrators and application prototypes.<br />

However, the model simply does not provide expressive enough abstractions<br />

to structure large and complex real-world sensor-network applications.<br />

Particularly, we believe that the model is lacking an abstraction to structure<br />

the program code along the time domain to describe program states or<br />

phases. The lack of such an abstraction is the fundamental reason <strong>for</strong> the above<br />

mentioned problems and is the starting point <strong>for</strong> developing our solution.<br />

Phases are distinct periods of time during which the program (or part of it)<br />

per<strong>for</strong>ms a single logical operation. A phase may include several events, which<br />

are handled within the context of the logical operation. <strong>Sensor</strong> nodes need to<br />

per<strong>for</strong>m a variety of ongoing operations that include several events. Examples


62 Chapter 4. Event-driven <strong>Programming</strong> in Practice<br />

are as gathering and aggregating sensory data, setting up and maintaining the<br />

network, <strong>for</strong>warding network packets, and so on. Typical sensor-node programs<br />

are structured in distinct and discrete phases, that is, the program’s operations are<br />

clearly separable and execute mostly sequentially.<br />

“Phase” is just one term that appears over and over in the WSN literature<br />

to describe the program structure of sensor-node applications and algorithms.<br />

Other frequently used terms with the same intention are “state”, “role”, “mode”,<br />

or “part”. Some authors even articulate that sensor-node programs in general<br />

are constructed as state machines [25, 37, 45, 80] because of their reactive nature.<br />

Notably, even multi-threaded programs are submitted to this view. Curiously,<br />

despite that fact that phases seem to be the prime concept in which application<br />

architects and programmers think, the predominant programming models <strong>for</strong><br />

sensor nodes have no explicit abstraction to specify such phases. Only the authors<br />

of NesC have identified the lack of state support in [45] and have put their<br />

support on the agenda <strong>for</strong> future work.<br />

We believe that the state abstraction of finite state machines is an adequate abstraction<br />

to model sensor-node program phases and that a programming model<br />

based on finite state machines can eventually solve the problems of the eventdriven<br />

model. Particularly, we propose a programming model based on concurrent<br />

and hierarchical state machines, as proposed in the seminal work on<br />

<strong>State</strong>charts [53] by Harel.<br />

In this chapter we will first examine the problem symptoms of the eventdriven<br />

model in Sect. 4.1. Then, in Sect. 4.2, we will present what we call the<br />

anatomy of sensor-node programs, that is, typical and recurring structures of<br />

phases in sensor-node programs and their interaction. We will explain these<br />

structural elements step-by-step based on a small example program, which is<br />

commonly found within the WSN literature. To illustrate this program’s structure<br />

we will use a small subset of the graphical <strong>State</strong>chart notation. Alongside<br />

the presentation of the anatomy, we will show the difficulties in expressing its<br />

structural elements in the event-driven model. And we will point out how these<br />

difficulties lead to the symptoms laid out in the first section of this chapter. In<br />

the last section of this chapter, Sect. 4.3, we will sketch the basic idea of how to<br />

solve the problems of the event-driven programming model <strong>for</strong> the specification<br />

of sensor-node programs. We propose an extended programming model, which<br />

combines elements of the event-driven programming model with those of finite<br />

state machines. A concrete solution will be presented in the next chapter.<br />

4.1 Limitations of Event-Driven <strong>Programming</strong><br />

In our work we have identified two issues with the otherwise very intuitive<br />

event-based programming model. These issues, which we call manual stack management<br />

and manual state management, arise because in the event-driven model<br />

many conceptual operations need to be split among multiple actions. In the<br />

following we will detail these issues and their causes.<br />

Since actions must not monopolize the CPU <strong>for</strong> any significant time, operations<br />

need to be non-blocking. There<strong>for</strong>e, at any point in the control flow where<br />

an operation needs to wait <strong>for</strong> some event to occur, the operation must be split


4.1. Limitations of Event-Driven <strong>Programming</strong> 63<br />

into two parts: a non-blocking operation request and an asynchronous completion<br />

event. The completion event then triggers an action that continues the<br />

operation. As a consequence, even a seemingly simple operation can lead to<br />

event cascades – an action calls a non-blocking operation, which causes an event<br />

to occur, which, in turn, triggers another action. Breaking a single conceptual<br />

operation across several actions also breaks the operation’s control flow and its<br />

local variable stack.<br />

Breaking conceptual operations among multiple functions has two implications<br />

<strong>for</strong> the programmer. Firstly, as the stack is unrolled after every action<br />

the programming language’s variable-scoping features are effectively discarded.<br />

Programmers cannot make use of local, automatically managed variables but<br />

instead need to manually manage the operation’s variable stack. This is called<br />

manual stack management [9]. Secondly, programmers must guarantee that any<br />

order of events is handled appropriately in the corresponding actions. This requires<br />

the manual intervention of the programmer by writing extra code. We<br />

call this manual state management. We use Program 4.1 as an example to clarify<br />

these issues.<br />

Program 4.1: Event-driven code fragment to compute the temperature average<br />

of sensor nodes in the one-hop vicinity.<br />

1 int sum = 0;<br />

2 int num = 0;<br />

3 bool sampling_active=FALSE;<br />

4<br />

5 void init_remote_average() {<br />

6 sampling_active=TRUE;<br />

7 sum = num = 0;<br />

8 request_remote_temp();<br />

9 register_timeout( 5 );<br />

10 }<br />

11 void message_hdl( MSG msg ) {<br />

12 if( sampling_active == FALSE ) return;<br />

13 sum = sum + msg.value;<br />

14 num++;<br />

15 }<br />

16 void timeout_hdl() {<br />

17 sampling_active=FALSE;<br />

18 int average = sum / num;<br />

19 /* ... */<br />

20 }<br />

Program 4.1 calculates the average temperature of sensor nodes in a one-hop<br />

distance. To do so, the sensor node running the program sends a broadcast<br />

message to request the temperature value from all neighboring sensor nodes<br />

(line 8). It then collects the remote samples in the event-handler <strong>for</strong> incoming<br />

radio messages (lines 11-15). A timeout is used to ensure the temporal contiguity<br />

of remote sensor readings. Finally, when the timeout expires, the average remote


64 Chapter 4. Event-driven <strong>Programming</strong> in Practice<br />

temperature is calculated (line 18). As shown in the code above, this relatively<br />

simple operation needs to be split into three parts: 1) sending the request, 2)<br />

receiving the replies, and 3) processing the result after the timeout.<br />

4.1.1 Manual Stack Management<br />

In the above example, the data variables sum and num are accessed within two<br />

actions. There<strong>for</strong>e sum and num cannot be local variables of either function.<br />

Instead they are declared as global variables, which have global scope and global<br />

lifetime.<br />

In a traditional, purely procedural program, local variables serve the purpose<br />

of keeping an operation’s local data. They are automatically allocated on the local<br />

runtime stack upon entering a function and are released on its exit. However,<br />

automatic variables cannot be used <strong>for</strong> event cascades since the local stack is unrolled<br />

after the execution of every action. There<strong>for</strong>e, the state does not persist<br />

over the duration of the whole operation. Instead, programmers must manually<br />

program how to retain the operation’s state. They can do so either using global<br />

variables (as in the example above) or by programming a state structure stored<br />

on the heap.<br />

Both approaches have drawbacks. The global variables have global lifetime,<br />

that is, they permanently lock up memory, also when the operation is not running.<br />

For example, a global variable used <strong>for</strong> a short phase during system initialization<br />

resides in memory <strong>for</strong> the rest of the program’s execution. Manually<br />

reusing this memory in different program phases is possible but highly errorprone.<br />

The (possibly multiple) programmers would have to keep track of the<br />

use of every variable’s memory in each program phase. Besides their global lifetime,<br />

global variables also have global scope. That is, their visibility extends to<br />

the entire program. As a consequence, global variables can be read and modified<br />

throughout the entire program. To avoid naming conflicts and accidental access,<br />

strict naming conventions have to be introduced and followed by programmers.<br />

For these reasons, it is generally considered good programming practice to avoid<br />

global variables and use local variables instead. Local variables have been one<br />

of the achievements of structured programming.<br />

The second approach, managing the operation’s state on the heap, requires<br />

manual memory management (e.g., by using malloc() and free()), which is<br />

generally considered error-prone. It also requires system support <strong>for</strong> dynamic<br />

memory management, which has a significant resource penalty and is there<strong>for</strong>e<br />

sometimes missing <strong>for</strong> resource-constrained sensor nodes. We have discussed<br />

dynamic memory management <strong>for</strong> sensor nodes in the previous Sect. 3.3.<br />

4.1.2 Manual <strong>State</strong> Management<br />

Depending on the node’s state and history of events, a program may need to<br />

(and typically does) behave quite differently in reaction to a certain event. In<br />

other words, the actual reaction to an event not only depends on the event but<br />

also on its context. This multiplexing between behaviors must be implemented<br />

explicitly by the programmer. As a result, programmers must include additional


4.1. Limitations of Event-Driven <strong>Programming</strong> 65<br />

management code, which obscures the program logic and is an additional source<br />

of error.<br />

In the previous Program 4.1, <strong>for</strong> example, replies from remote sensors should<br />

only be regarded until the timeout expires and thus the timeout action is invoked.<br />

After the timeout event, no more changes to sum and num should be<br />

made (even though this is not critical in the concrete example). To achieve this<br />

behavior, the timeout action needs to communicate with the radio-message action<br />

so that no more replies should be regarded. Manual state management is<br />

highlighted in the program code. The desired behavior is en<strong>for</strong>ced by introducing<br />

a (global) boolean flag sampling_active (line 3), which is set to indicate<br />

the state of the aggregation operation. The flag is used in all three functions<br />

(lines 6, 12, and 17). In the message action, the program checks whether the<br />

timeout has occurred already and thus remote temperature values should no<br />

longer regarded (line 12).<br />

In general, programmers using the event-driven programming model must<br />

manually manage the program state in order to be able to decide in which context<br />

an action has been invoked. <strong>Based</strong> on that state in<strong>for</strong>mation programmers<br />

must multiplex the program’s control flow to the appropriate part of the action.<br />

We call this manual state management. Coding the flow control manually requires<br />

operating on state that is shared between multiple functions (such as the<br />

flag sampling_active in our example). Again, this state needs to be managed<br />

manually by the programmers. A general pattern <strong>for</strong> a well-structured implementation<br />

of context-sensitive event handling is shown in Program 4.2.<br />

Accidental Concurrency<br />

Manually managing the program state may lead to accidentally (re)starting<br />

an event cascade, which we call accidental concurrency. In our example,<br />

the initialization function init_remote_average() and the two actions<br />

(message_hdl() and timeout_hdl()) implement a single logical operation<br />

that runs <strong>for</strong> some period of time (i.e., the aggregation phase). The parts of that<br />

operation belong inherently together and it is implicitly expected that the entire<br />

phase has been completed be<strong>for</strong>e it is started anew. However, our example<br />

is prone to accidental concurrency. The event cascade implementing the aggregation<br />

phase could be started anew with a call to init_remote_average()<br />

(perhaps from an action handling incoming network packets <strong>for</strong> re-tasking the<br />

node). Restarting the aggregation phase while a previously started aggregation<br />

phase is still running will lead to unexpected behavior and thus impair the reliability<br />

of the program. In our example, the global variables sum and num will<br />

be reset to zero, yielding an incorrect result <strong>for</strong> the previous instantiation of the<br />

remote-average operation. The topic of accidential concurrency is closly related<br />

to reentrant functions in multithreaded systems. Just like functions, most event<br />

cascades are not reentrant when they rely on global variables to store “private”<br />

state. In event-driven programming, however, global variables need to be used<br />

often, as explained in the previous section.<br />

Accidential concurrency is not limited to multiple instances of the same cascade<br />

of events. Some locical operations (i.e., event cascades) may be mutually<br />

exclusive or may need to be executed in a particular order. Events that occur


66 Chapter 4. Event-driven <strong>Programming</strong> in Practice<br />

Program 4.2: General pattern to implement context sensitive actions.<br />

1 typedef enum {INIT, A, B, ... } state_t;<br />

2 state_t state = INIT; // initial state<br />

3<br />

4 void event_handler_x {<br />

5 switch( state ) {<br />

6 case INIT:<br />

7 // handle event_x in state INIT<br />

8 state = ...; // set new state<br />

9 break;<br />

10 case A:<br />

11 // handle event_x in state A<br />

12 state = ...; // set new state<br />

13 break;<br />

14 case B:<br />

15 // handle event_x in state B<br />

16 state = ...; // set new state<br />

17 break;<br />

18 default:<br />

19 // ...<br />

20 }<br />

21 }<br />

22 void event_handler_y {<br />

23 // ...<br />

24 }<br />

when the programmer was not expecting them (e.g., duplicated network messages<br />

or events generated by another action), however, will trigger their associated<br />

operation regardless. To en<strong>for</strong>ce the correct behavior, programmers need to<br />

manage contex in<strong>for</strong>mation and need to code the program’s flow control explicitly.<br />

In the event-driven model the reaction to an event cannot be specified with<br />

respect to such context in<strong>for</strong>mation. Instead an event always triggers its single<br />

associated action. The event model does not provide any means against restarting<br />

of such phases anew. (In fact, there is not even an abstraction to indicate<br />

which parts of the code implement a single logical operation.) Special care must<br />

be taken by programmers not to accidentally restart a cascade of actions. In oder<br />

to be able to detect such errors, a defensive programmer would set a flag at the<br />

beginning of the logical operation and check it in every action to trigger exception<br />

handling. For the example Program 4.1, this could be achieved by checking<br />

whether every invocation of init_remote_average()) has been followed by<br />

a call to timeout_hdl() or otherwise ignore the invocation. The following additional<br />

code implements a possible solution when inserted after line 5 of the<br />

example:<br />

if( sampling_active == TRUE ) {<br />

return; // error, ignore<br />

}


4.1. Limitations of Event-Driven <strong>Programming</strong> 67<br />

Impaired Modularity and Extensibility<br />

Now consider adding a new feature to the system, <strong>for</strong> example, <strong>for</strong> re-tasking<br />

the node. Say, the new subsystem interprets a special network-message event,<br />

which allows to configure which type of sensor to sample. Thus, the new subsystem<br />

requires a message handler, such as the one in line 11 of Program 4.1.<br />

Since each type of event always triggers the same action, both the sensing subsystem<br />

and the new re-tasking subsystem need to share the single action <strong>for</strong><br />

handling network messages (message_hdl() in the example). The code fragment<br />

in Program 4.3 shows a possible implementation. Generally, extending the<br />

functionality of a system requires modifying existing and perfectly good code<br />

in actions that are shared among subsystems. Having to share actions between<br />

subsystems clearly impairs program modularity and extensibility.<br />

Program 4.3: An action shared between two subsystems of a node (<strong>for</strong> aggregating<br />

remote sensor values and re-tasking).<br />

1 void message_hdl( MSG msg ) {<br />

2 if( msg.type == RETASKING )<br />

3 // re-tasking the node<br />

4 // ...<br />

5 else {<br />

6 // aggregating remote values<br />

7 if( sampling_active == FALSE ) return;<br />

8 sum = sum + msg.value;<br />

9 num++;<br />

10 }<br />

11 }<br />

4.1.3 Summary<br />

In the small toy example we have just presented, manual state and manual stack<br />

management seems to be a minor annoyance rather than a hard problem. However,<br />

even in such a simple program as Prog. 3.5, a significant part of the code<br />

(4 lines plus one to avoid accidental concurrency) is dedicated to manual flow<br />

control. Even minor extensions cause a drastic increase in the required management<br />

code. As application complexity grows, these issues become more and<br />

more difficult to handle. In fact, in our applications that implement complex<br />

networking protocols (e.g., our Bluetooth stack), significant parts of the code are<br />

dedicated to manual state and stack management. The code is characterized by<br />

a multitude of global variables, and by additional code in actions to manage the<br />

program flow. This code obscures the program logic, hampers the program’s<br />

readability, and is an additional source of error.<br />

In general, the event-driven programming model implies to structure the program<br />

code into actions. Actions typically crosscut several logical operations (i.e.,<br />

phases or subsystems), which hampers program modularity and extensibility.<br />

If functionality is added to or removed from the software, several actions and


68 Chapter 4. Event-driven <strong>Programming</strong> in Practice<br />

thus subsystems are affected. Also, the event-driven programming model lacks<br />

methods to hide the private data of program phases from other phases, which<br />

also negatively effects the program structure and modularity. Finally, because<br />

local variables cannot be used across multiple actions, the model makes it hard<br />

to reuse memory, which results in memory inefficient programs. These symptoms<br />

of event-driven programs are by no means restricted to our event-based<br />

programming framework <strong>for</strong> BTnodes. Similar issues can be found in program<br />

examples <strong>for</strong> the popular TinyOS / NesC framework in [45] or <strong>for</strong> SOS in [52].<br />

4.2 The Anatomy of <strong>Sensor</strong>-Node Programs<br />

Most sensor-network programs and algorithms are structured along discrete<br />

phases. An example where the composition of sensor-node programs into distinct,<br />

sequentially scheduled phases becomes particularly apparent is role assignment:<br />

The node only per<strong>for</strong>ms the single currently assigned role out of several<br />

possible roles (see <strong>for</strong> example [42, 56, 98]. When a new role needs to be<br />

assigned, the previous role is first terminated be<strong>for</strong>e the newly assigned role is<br />

started.<br />

In fact, a prime application of sensor networks is to detect certain states of<br />

the real world, that is, phases during which a certain environmental condition is<br />

met. In applications where the state detection is per<strong>for</strong>med partly or entirely on<br />

the sensor nodes (as opposed to in the background infrastructure based on the<br />

in<strong>for</strong>mation collected by sensor nodes) these real-world states are often modeled<br />

discretely <strong>for</strong> the sake of simplicity. Detecting a change in the real-world<br />

state then typically triggers a new phase in the sensor-node program in order<br />

to react to the change in a state-specific manner. A typical reaction is to collect<br />

additional in<strong>for</strong>mation specific to the detected state and to notify the rest of<br />

the sensor network of the detection. This phase continues until the real-world<br />

state changes again, which is reflected by yet another phase change of the program.<br />

Examples are a health monitor, which determines the stress state of a<br />

human user to be high, normal, or low [55], an augmented mobile phone, which<br />

automatically adapts it ring-tone-profile based on the detected phone context<br />

(in-hand, on-table, in-pocket, and outdoors) [46], several tracking applications<br />

(tracking target is detected or absent) [6, 100, 118], product monitoring (product<br />

damaged or intact) [107], and cattle herding (animal is inside or outside of a<br />

virtually fenced in region) [28].<br />

4.2.1 Characteristics of a Phase<br />

We have analyzed the source code of numerous programs and program fragments<br />

(both from the literature as well as our own) if and how program phases<br />

are reflected in the code. We have found that phases can indeed be identified in<br />

program sources. The phases of programs and algorithms do share four common<br />

characteristics by which they can be recognized: (1) they run <strong>for</strong> a certain<br />

period of time, during which they may need to handle several events, (2) their<br />

beginning and end are typically marked by (one or several) specific events, (3)<br />

they are strongly coupled, that is, the computational operations within a phase


4.2. The Anatomy of <strong>Sensor</strong>-Node Programs 69<br />

utilize a common and clearly defined set of resources, and (4) they start with<br />

an initialization function where the (temporary) resources <strong>for</strong> the phase are allocated<br />

and they end with a deinitialization function where the resources are<br />

released again.<br />

Individual phases have a strong internal coupling with respect to other<br />

phases. Typically this means two things: Firstly, the computations belonging<br />

to a phase operate on a common data structure that is private with respect to<br />

other phases (though phases do typically communicate via small amounts of<br />

shared data). And secondly, during a phase the program utilizes a well defined<br />

set of resources, which may differ significantly from other phases. For example,<br />

phases differ in the amount of computation taking place (CPU cycles), the size<br />

of the data structures being used (memory), which sensors are being sampled<br />

and at what rates, and whether the radio is actively sending, in receive mode, or<br />

switched of entirely.<br />

In the program code, the start of a phase is typically marked with an initialization<br />

function and ends with a deinitialization function (cf. [52]). In initialization,<br />

the resources required by the phase are allocated and its data structures are set to<br />

their initial values. Resource allocation can mean that memory <strong>for</strong> holding private<br />

data structures is allocated, that timers are set up, and that sensors or the<br />

radio are switched on. At the end of a phase, in<strong>for</strong>mation computed during the<br />

phase may be made available <strong>for</strong> use by other (i.e., concurrent or subsequent)<br />

phases, <strong>for</strong> example, by saving it into a shared data structure. Then the phase’s<br />

resources are released.<br />

4.2.2 Sequential Phase Structures<br />

In the previous section we have described the characteristics of an individual<br />

phase. We will now describe the phase structure of sensor-node programs,<br />

that is, typical and recurring patterns of how programs are constructed of individual<br />

phases. This description is based on the following running example.<br />

Consider a monitoring application that samples an environmental parameter<br />

(e.g., the light intensity) at regular intervals and then immediately sends<br />

the data sample to a dedicated sensor node. This simple application (sometimes<br />

named “Surge”, “sense and <strong>for</strong>ward” or “sample and send”) is used with<br />

minor modifications throughout the sensor-network literature to demonstrate<br />

programming-framework features [8, 21, 45, 48, 52, 79] or to do per<strong>for</strong>mance<br />

evaluations [36, 48, 79]. We will use this example and modifications of it to describe<br />

the phase structure of sensor-node programs. As a matter of fact, this<br />

application is so simple that the code fragments found in the literature typically<br />

treat the whole program as a single entity with a single data structure and all<br />

initialization per<strong>for</strong>med at the start of the program. Yet, we will gradually add<br />

features and treat it like a full featured sensor-node program to explain program<br />

structures with multiple phases and their interactions.<br />

Surge Example 1<br />

A simple version of Surge consists of four phases, which we will call<br />

INIT, SAMPLE, SEND, and IDLE, as depicted in Fig. 4.1. The program<br />

starts with the INIT phase, which per<strong>for</strong>ms the general system initialization.<br />

When the INIT phase is complete, the program enters the


70 Chapter 4. Event-driven <strong>Programming</strong> in Practice<br />

SAMPLE phase in order to start sampling. In order to save power the<br />

sensor is generally switched off unless actually sampling. There<strong>for</strong>e<br />

the SAMPLE phase is initialized by switching on the sensor. When<br />

the data sample is available, which may considerable time (e.g., <strong>for</strong><br />

GPS sensors), the sensor is switched off again. Then the SEND phase<br />

is started. Similar to the SAMPLE phase the SEND phase first switches<br />

on the radio, waits until a connection to the dedicated sink node is<br />

established and then sends the data sample acquired in the previous<br />

SAMPLE phase. When the reception of the data has been acknowledged<br />

by the sink node, the radio is switched off again and the program<br />

moves to the IDLE phase. In this initial version of the program<br />

we ignore a failed send attempt (e.g, due to collisions or packet loss<br />

on a noisy channel) and move to IDLE anyway. The IDLE phase does<br />

nothing but wait <strong>for</strong> the start of the next interval, when the next cycle<br />

of sampling and sending is restarted, that is, the program moves to<br />

the SAMPLE phase again. In order to sleep <strong>for</strong> the specified time, a<br />

timer is initialized upon entering the IDLE phase and then the CPU is<br />

put into sleep mode. The node wakes up again when the timer fires<br />

and the program moves to the SAMPLE mode again. In our example,<br />

the sampling of the sensor in the SAMPLE state may need to be<br />

scheduled regularly. Since both the sample and the send operation<br />

may take varying amounts of time, the program needs to adjust the<br />

dwell period in the IDLE phase. ✷<br />

Start INIT SAMPLE SEND<br />

init_done sample_done<br />

(n)ack<br />

IDLE<br />

timeout<br />

Figure 4.1: A simple version of Surge with four program phases. In this version<br />

a failure while sending the data sample is ignored.<br />

Though this program is rather simple, it has many features of the complex<br />

algorithms used in current sensor-node programs.<br />

Sequential Composition<br />

The four phases of the Surge program are all scheduled strictly sequentially.<br />

From our analysis of sensor-node programs we conclude that typically the majority<br />

of logical operations (i.e., phases) are per<strong>for</strong>med sequentially (though<br />

there is a certain amount of concurrency, as we will discuss in Sect. 4.2.3).<br />

Whenever the application at hand permits, architects of sensor-node programs<br />

seem to be inclined to schedule these phases sequentially rather then<br />

concurrently. This holds true even <strong>for</strong> many fundamental system services, such<br />

as network management, routing, device management, etc. This is in contrast to<br />

traditional networks, where most of such services are per<strong>for</strong>med permanently in<br />

the background, often by dedicated programs or even on dedicated hardware.


4.2. The Anatomy of <strong>Sensor</strong>-Node Programs 71<br />

We think that the composition of sensor-node programs predominantly into<br />

distinct, sequential phases has two main reasons: Firstly, sequential programs<br />

are typically easier to master than concurrent programs, as they require little<br />

explicit synchronization. And secondly, and even more importantly, the phases<br />

of a sequential program do not require coordination of resources and can thus<br />

utilize the node’s limited resources to the full, rather than having to share (and<br />

synchronize) between concurrent program parts.<br />

One difficulty of distinct, sequential program phases in the event-driven<br />

model lies in the specification and the memory efficient storage of variables used<br />

only within a phase. As we discussed earlier, such temporary variables must<br />

typically be stored as global variables, with global scopes and lifetimes. From<br />

the program code it is not clearly recognizable when a phase ends and thus the<br />

variables’ memory can be reused.<br />

Starting and Completing Phases<br />

In sensor-network programs a phase often ends with the occurrence of a particular<br />

event, which then also starts the next phase. Often this event is an indication<br />

that a previously triggered operation has completed, such as a timeout event<br />

triggered by a timer set previously, an acknowledgment <strong>for</strong> a network message,<br />

or the result of a sensing operation. So it can often be anticipated that a particular<br />

event will occur, is often hard or impossible to predict when.<br />

A phase may also end simply because its computation has terminated, like the<br />

INIT phase in the previous example. Then the next operation starts immediately<br />

after the computation. It may still be useful to view both phases as distinct<br />

entities, particularly if one of them can be (re-)entered along a different event<br />

path, as in the above example.<br />

The specification of phase transitions in the event-driven programming model<br />

poses the a<strong>for</strong>ementioned modularity issue. Since the transition is typically triggered<br />

by a single event, both the initialization and deinitialization functions<br />

need to be per<strong>for</strong>med within the single associated event handler. There<strong>for</strong>e,<br />

modifications to the program’s phase structure causes modifications to all event<br />

handlers that lead in and out of added and removed phases.<br />

Also, phase transitions incur manual state management if different phases are<br />

triggered by the same event. Then the associated event handler must be able<br />

to determine which phase’s initialization to per<strong>for</strong>m. This can only be done<br />

by manually keeping the current program state in a variable and checking the<br />

previous state at the beginning of each action.<br />

Repeated Rounds of Phases<br />

Often phases of sensor-node programs are structured in repeated rounds. A<br />

typical example is the cyclic “sample, send and sleep” pattern found in our example<br />

application, as well as in many sensor-node programs described in the<br />

literature. Examples where repeated rounds of distinct, sequentially scheduled<br />

program phases are used are LEACH [55, 56], TinyDB and TAG [83, 84], and<br />

many more.


72 Chapter 4. Event-driven <strong>Programming</strong> in Practice<br />

Choosing the next phase: Branching<br />

Often there are several potential next phases in sensor-network programs. The<br />

choice of the actual next phase then depends on an input event, the state of a<br />

previous computation, or a combination of both. Also, a single phase may be<br />

reachable through multiple paths, as in the following example.<br />

Start<br />

Surge Example 2<br />

Until now our Surge application ignored a failed sending attempt.<br />

If sending fails, however, the program may decide to store the data<br />

sample in a local buffer (rather then ignoring the failure). The modified<br />

program could then, <strong>for</strong> example, send the stored data sample<br />

in the next round together with the new sample. An updated version<br />

of the program is depicted in Fig. 4.2. In the new version, the<br />

choice of the next phase from SEND depends on the event generated<br />

by the send operation (nack or ack). Likewise, the IDLE phase may<br />

be reached from two phases, namely SEND and STORE. ✷<br />

INIT SAMPLE<br />

init_done sample_done<br />

SEND<br />

nack<br />

ack<br />

STORE<br />

IDLE<br />

store_done<br />

timeout<br />

Figure 4.2: A more sophisticated version of Surge with five program phases. In<br />

this program version a failure while sending the data sample is handled<br />

by buffering the data locally. Note that the control flow branches<br />

from the SEND phase depending on the events nack and ack.<br />

Preemption<br />

In sensor-node programs the occurrence of a certain event often interrupts (we<br />

say it preempts) a sequence of multiple phases in order to start a new phase. That<br />

is, if the event occurs in any phase of a sequence of phases, the control flow of<br />

the program is transfered to the new phase.<br />

Surge Example 3<br />

Let us now consider yet another variation of the basic Surge program,<br />

which is inspired by the modification of Surge as distributed<br />

with TinyOS [125]. In this version we introduce a network-command<br />

interface that allows a remote user to put the node in sleep mode by<br />

sending a sleep message to it. The node then preempts all current<br />

activities and sleeps until it receives a subsequent wakeup message.<br />

This version of Surge is depicted in Fig. 4.3. ✷<br />

The dashed lines in Fig. 4.3 denote the preemption caused by the sleep event.<br />

That is, the control is passed to the SLEEP phase from any of the phases SAMPLE,<br />

SEND, and IDLE on the occurrence of the sleep event.


4.2. The Anatomy of <strong>Sensor</strong>-Node Programs 73<br />

INIT<br />

init_done<br />

NORMAL_OPERATION<br />

SAMPLE<br />

sample_done (n)ack timeout<br />

SEND<br />

wakeup sleep<br />

SLEEP<br />

IDLE<br />

Figure 4.3: The three program phases SAMPLE, SEND, and IDLE are preempted<br />

by the sleep event.<br />

The execution of the target phase of a preemption takes priority over the preempted<br />

phases. Preemption is basically the same as what we have previously<br />

called branching, only that preemption has a number of source phases (rather<br />

then just one). Preemption is can be considered as handling of exceptions. The<br />

phase to which control is transferred to may then be considered the exception<br />

handler.<br />

Specifying preemption in the event-model requires extensive state management.<br />

The difficulty is two-fold: Firstly, on the occurrence of a preempting event,<br />

the programmer must first deinitialize the phases that are to be preempted. Special<br />

care must be taken if preemption is added during the redesign of a sensornode<br />

program. From our experience, proper deinitialization can be easily <strong>for</strong>gotten.<br />

And secondly, (future) events that are caused by computational operations<br />

per<strong>for</strong>med be<strong>for</strong>e the preemption occurred must be actively ignored. For<br />

example, in our example program the (n)ack event is caused by sending a data<br />

sample. If the send operation has completed, but a sleep event is received next<br />

(i.e., be<strong>for</strong>e the (n)ack event), the (n)ack event is nevertheless generated by the<br />

OS and thus triggers the associated action. In this action, however, it needs to be<br />

ignored. The code fragment in Prog. 4.4 shows a possible implementation of the<br />

preemption in the event-driven model. The handler of the sleep event (lines 1-<br />

14) implements the preemption. The preempted phases are deinitialized within<br />

the switch statement (lines 3-11). The handler of the nack event first checks the<br />

exceptional case when the program is in the SLEEP state (lines 16). Then the<br />

nack event is ignored. If the program was coming from the SEND phase (which<br />

is the regular case), it behaves as without preemption. Clearly, the complexity of<br />

manual state management in the case of preemption is hard to handle <strong>for</strong> programmers.<br />

Even this short program is hard to write and even harder to read. To<br />

deduce a programmers intention from the code is next to impossible.<br />

Structuring Program Phases: Phase Hierarchies<br />

In some cases, it is convenient to think of a set of sequentially composed phases<br />

as a single, higher-level program phase. This implies that there is a hierarchy of<br />

phases, where phases higher in the hierarchy (which we call super-phases) are<br />

composed of one or more sub-phases. Our previous example, Fig. 4.3, is such a<br />

case.


74 Chapter 4. Event-driven <strong>Programming</strong> in Practice<br />

Program 4.4: A code fragment showing the full deinitialization procedure that<br />

needs to be per<strong>for</strong>med on a preemption.<br />

1 void sleep_hdl() {<br />

2 // deinitialize previous phases<br />

3 switch( sate ) {<br />

4 case SAMPLE: ... // deinitialize SAMPLE phase: switch off sensor<br />

5 break;<br />

6 case SEND: ... // deinitialize SEND phase: switch off radio<br />

7 break;<br />

8 case IDLE: ... // deinitialize IDLE phase: reset timer<br />

9 break;<br />

10 default: ... // error handling<br />

11 }<br />

12 state = SLEEP;<br />

13 // do sleep ...<br />

14 }<br />

15 void nack_hdl() {<br />

16 if( state == SLEEP ) {<br />

17 // ignore the nack event while in SLEEP<br />

18 return;<br />

19 }<br />

20 // deinitialize the SEND phase: switch off radio<br />

21 state = IDLE;<br />

22 // per<strong>for</strong>m initialization and operations <strong>for</strong> IDLE<br />

23 }<br />

The complete cycle of sampling, sending, and idling can be considered a phase<br />

by itself, representing the period where the node per<strong>for</strong>ms its normal operation.<br />

The new situation is depicted in Fig. 4.4, where we have indicated the superphase<br />

by surrounding the contained phases with a solid box. The new high-level<br />

phase NORMAL_OPERATION is initially entered by the init_done event coming<br />

from INIT phase. It may be re-started by the wakeup event after residing temporarily<br />

in the SLEEP phase. NORMAL_OPERATION ends on the occurrence of<br />

the sleep event. Then the high-level phase as well as all contained phases are<br />

preempted and control is passed to the SLEEP phase.<br />

Super-phases are a convenient mind model that allows to subsume a complex<br />

subsystem into a single, less-refined subsystem. Hierarchical phase structures<br />

help program architects to develop a clear and easy to understand model of<br />

the program. These structures also foster communication about the model on<br />

different levels of detail. It is easy to “abstract away” the details of a superphase<br />

(top-down view, see Fig. 4.5) or, on the contrary, to first focus only on the<br />

details and building the system from the ground up (bottom-up view).<br />

Super-phases typically have the same characteristics as uncomposed phases.<br />

That is, the entire super-phase may have its own initialization and deinitialization<br />

function, as well as data structures. Then, all sub-phases (i.e., all composed<br />

phases) share that common data structure. The super-phases start when<br />

a contained phase starts and ends when the last of the contained phases is left.


4.2. The Anatomy of <strong>Sensor</strong>-Node Programs 75<br />

INIT<br />

init_done<br />

NORMAL_OPERATION<br />

SAMPLE<br />

sample_done (n)ack<br />

wakeup<br />

SEND<br />

SLEEP<br />

sleep<br />

IDLE<br />

timeout<br />

Figure 4.4: A refinement of Fig. 4.3. The three phases SAMPLE, SEND, and IDLE<br />

have been combined into the super-phase NORMAL_OPERATION.<br />

The super-phase is preempted by the sleep event.<br />

INIT<br />

init_done<br />

NORMAL_OPERATION<br />

...<br />

wakeup sleep<br />

SLEEP<br />

Figure 4.5: An abstraction of NORMAL_OPERATION from Fig. 4.4.<br />

There may be a dedicated initial phase when starting the composed phase, as<br />

the SAMPLE phase in our example (indicated by the arrow origination from a<br />

circle). However, there may be several initial states, where the actual initial<br />

state depends on the result of a previous computation, the event that caused<br />

the start of the composed phase, or the phase from which control was passed<br />

to the composed phase. The problem of implementing hierarchical phases in<br />

the event-driven model is to correctly per<strong>for</strong>m the (de)initialization functions<br />

correctly and to track resource usage within sub-phases.<br />

In the sensor-network literature there are several examples where authors describe<br />

their algorithms as hierarchical phases. The LEACH algorithm [56] (a<br />

clustering-based communication protocol to evenly distribute the energy load<br />

among the sensors in the network), <strong>for</strong> example, is described as being “broken<br />

into rounds, where each round begins with a set-up phase [...] followed by a<br />

steady-state phase”. The set-up phase is further composed of three sub-phases<br />

called advertisement, cluster set-up, and schedule creation.<br />

Phase-Bound Lifetime and Scope of Data Structures<br />

<strong>Sensor</strong>-node programs often operate on data structures that are temporary in<br />

nature. That is, the use of such data structures (just like other resources) is typically<br />

closely tied to a phase or a sequence of multiple phases; the structures are<br />

accessed only during certain phases of the program. Typically, programs use<br />

several data structures in different phases, that is, they have different lifetimes.<br />

A challenge in programming sensor nodes is the efficient management of data<br />

structures, since memory is one of the highly-constraint resources. That means


76 Chapter 4. Event-driven <strong>Programming</strong> in Practice<br />

that programmers typically strive to constrain the lifetime of data structures to<br />

those program phases in which they are actually needed. To summarize, the<br />

lifetime of temporary data structures must extend to all phases that share the<br />

data, but should not extend beyond those phases. That is, after the data structures<br />

are no longer needed, the memory should be released in order to recycle<br />

the memory in distinct phases.<br />

In our example, the SAMPLE and SEND phases both need access to the sampled<br />

data, which may have an arbitrary complex in-memory representation depending<br />

on the sensor and application. There<strong>for</strong>e, the lifetime of the data sample<br />

must extend to both the SAMPLE and SEND phases. After sending the value the<br />

data becomes obsolete. The memory <strong>for</strong> the data could be reused in subsequent<br />

phases. Since in our example the IDLE phase does not have extensive memory<br />

requirements, there would be not much point. In real-world sensor-node programs,<br />

however, memory reuse is imperative.<br />

Again, in LEACH [56], <strong>for</strong> example, individual phases with seizable memory<br />

requirements alternate. The operation of each sensor node participating<br />

in the LEACH algorithm is broken up into rounds, where each round begins<br />

with a cluster-organization phase, followed by a data-aggregation, and data<strong>for</strong>warding<br />

phase. In the cluster-organization phase each node collects a list<br />

of all network neighbors annotated with various in<strong>for</strong>mation, such as receivedsignal<br />

strength. In a dense network, this list can be of considerable size. During<br />

the phase, each node chooses a cluster head and may become one itself. After<br />

completion of the phase, only the identity of the cluster head is required, the<br />

neighbor list however has become obsolete and can be freed. The outcome of<br />

the cluster-organization phase is shared with the rest of the program by means of<br />

shared memory. Every node that has become a cluster head then starts collecting<br />

data samples from their slaves, aggregates the received data, and finally sends<br />

the aggregate to its own cluster head, whose identity has been kept from the<br />

cluster-organization phase. Here, releasing the temporary data structure (i.e.,<br />

the neighbor list) frees up memory that might be required during data collection<br />

and aggregation.<br />

In the event-driven programming model programmers have two alternatives<br />

to store such temporary data: global variables and dynamic memory. As we<br />

have previously shown in Sect. 4.1, both approaches have severe drawbacks.<br />

4.2.3 Concurrency<br />

While sensor-node programs are often geared towards sequential operation, it<br />

may still be necessary or convenient to run some operations concurrently. Probably<br />

the most common concurrent activities per<strong>for</strong>med by sensor-node programs<br />

are sampling and maintenance tasks, such as network management. For example,<br />

in EnviroTrack [6], a group-based protocol <strong>for</strong> tracking mobile targets,<br />

sensing runs concurrently to group management.<br />

As we pointed out in a previous chapter, in sensor-node programming, concurrency<br />

is typically a purely logical concept because sensor nodes are singleprocessor<br />

systems. Concurrency is a descriptive facility <strong>for</strong> the programmer to<br />

describe self-contained and concurrently executing parts of the system in individual<br />

programmatic entities, without having to care about their actual execu-


4.2. The Anatomy of <strong>Sensor</strong>-Node Programs 77<br />

tion. The system software takes care of scheduling these entities so that they<br />

appear to run concurrently, while they are actually scheduled sequentially on<br />

the single processor in time multiplex.<br />

The event-driven programming model has no notion of concurrency, such as<br />

the thread abstraction in multi-threaded programming. Indeed, it does not even<br />

have a programming abstraction to group together the logical operations and<br />

self-contained system parts. As we have discussed in the previous sections, despite<br />

the lack of a dedicated programming abstraction, sensor-node programmers<br />

tend to think and structure their programs in terms of hierarchical phases.<br />

The phase model lends itself well to modeling concurrent program parts.<br />

INIT<br />

Example Surge 4<br />

Let us consider a final addition to our Surge program, which is inspired<br />

by the ZebraNet application [81]. Every node in ZebraNet<br />

runs of a battery that is recharged using solar cells. Since normal operation<br />

draws more power than the solar cells can supply, just be<strong>for</strong>e<br />

the battery is empty the node goes into sleep mode and recharges<br />

the battery. In ZebraNet this behavior is implemented by regularly<br />

checking the remaining battery level. Should it drop below a certain<br />

threshold, the node initiates charging, during which normal operation<br />

is suspended.<br />

Checking the battery status and charging can be considered two<br />

distinct program phases, as depicted in Fig. 4.6. These phases,<br />

CHECK and CHARGE, may have multiple sub-phases each. Combined<br />

into the BATTERY_OPERATION super-phase) they run concurrently<br />

with the ENVIRONMENT_MONITORING super-phase (i.e., the<br />

rest of the system with the exclusion of the IDLE phase). ✷<br />

init_done<br />

ENVIRONMENT_MONITORING<br />

NORMAL_OPERATION<br />

SAMPLE<br />

BATTERY_OPERATION<br />

sample_done (n)ack timeout<br />

CHECK<br />

...<br />

SEND<br />

wakeup sleep<br />

SLEEP<br />

batt_empty<br />

batt_full<br />

IDLE<br />

CHARGE<br />

...<br />

Figure 4.6: A version of Surge with two concurrent phases ENVIRON-<br />

MENT_MONITORING and BATTERY_OPERATION.


78 Chapter 4. Event-driven <strong>Programming</strong> in Practice<br />

Interaction Between Phases: Local Communication<br />

Typically the various concurrent phases of sensor-node programs do need to<br />

communicate. They run mostly independently of each other (after all, that was<br />

the reason why to model them as concurrent phases), but from time to time,<br />

they may still require some synchronization. In sensor node programs two principal<br />

methods of communication between concurrent phases are used: communication<br />

with generated events and transmitting in<strong>for</strong>mation in data structures<br />

residing shared memory. The latter <strong>for</strong>m is associated with polling while the <strong>for</strong>mer<br />

is associated with handling event notifications, the basic concept of eventdriven<br />

programming.<br />

Synchronization with Events. In our example as depicted in Fig. 4.6, whenever<br />

the battery needs recharging the program enters the CHARGE state. To<br />

suspend normal operation when recharging, the battery subsystem needs to<br />

communicate with the concurrent monitoring subsystem. There are two possibilities<br />

of how to synchronize the two concurrent subsystems with events:<br />

Firstly, the monitoring subsystem is programmed to react to batt_empty events<br />

by preempting normal operation and by going to sleep. The second possibility<br />

is to generate a sleep event when making the transition from checking to charging<br />

in the battery subsystem. Likewise, whenever a remote user instructs the<br />

node to enter sleep mode through the network-command interface, the node<br />

could (and should) start charging the battery. In this example, the phases ENVI-<br />

RONMENT_MONITORING and BATTERY_OPERATION are synchronized directly<br />

through events, their sub-phases (NORMAL_OPERATION and SLEEP as well as<br />

CHECK and CHARGE) progress in lock step.<br />

Synchronization with Shared Memory and Events. Concurrent phases can communicate<br />

using shared memory. To avoid polling, shared-memory communication<br />

is often initiated by an event notification. Only after receiving a notification<br />

the program reads a shared memory location. In our BTnode system software,<br />

<strong>for</strong> example, the Bluetooth driver can be considered as running concurrently to<br />

the user application. The user application relies on notifications by the Bluetooth<br />

driver to indicate network-state changes, such as remote connection establishment<br />

or link failure. The driver does so by generating an event specific to the<br />

state change. In the user program the generated event then triggers a reaction.<br />

The user program typically requires more in<strong>for</strong>mation about the event (such as<br />

the identity of the connecting node or the identity of the failed link destination).<br />

This in<strong>for</strong>mation can be be accessed through shared memory, concretely,<br />

through a shared connection table, a data structure which is maintained by the<br />

Bluetooth driver but can be accessed by the application as well.<br />

The Absence of Dynamic Task Creation<br />

In our study of existing sensor-node programs we have found several examples<br />

where program phases run concurrently. But we have not found programs that<br />

require the dynamic creation of (a previously unknown number of) tasks. Multithreading<br />

environments are often motivated with a Web-server example, where<br />

individual HTTP-requests are serviced by a separate and possible dynamically


4.3. Extending the Event <strong>Model</strong>: a <strong>State</strong>-<strong>Based</strong> Approach 79<br />

created threads. (cf. [108, 115]). In such situations, the number of active tasks at<br />

any point during runtime is not known at compile time.<br />

In sensor networks, however, programs are modeled with a clear conception<br />

how of many concurrent subsystems (i.e., program phases) there are and when<br />

they are active during the runtime of the program. The only circumstances<br />

where we did find a <strong>for</strong>m of dynamic creation of concurrent program phases<br />

where errors in the program specification, such as those described as accidental<br />

concurrency in Sect. 4.1.<br />

4.2.4 Sequential vs. Phase-<strong>Based</strong> <strong>Programming</strong><br />

Most algorithms are described as hierarchical and concurrent phases in the WSN<br />

literature. However, since there are no adequate programming abstractions and<br />

language elements to specify phases in actual programs, it is typically very hard<br />

to recognize even a known phase structure of a given program.<br />

In many respects the phases of sensor-node programs are comparable to functions<br />

of a sequential, single threaded program. Sequential programs are composed<br />

of function hierarchies, whereas sensor node programs are composed of<br />

phase hierarchies. Functions may have private data structures (typically specified<br />

as local variables) but may also operate on shared data (the local variables<br />

of a common higher function hierarchy or global variables). Program phases of<br />

sensor-node program also have these private and shared data structures, however,<br />

programming abstractions to specify them are missing.<br />

The main difference of functions and phases is that the duration of a function<br />

is typically determined by the speed of the CPU, whereas the duration of a<br />

phase mostly depends on the occurrence of events, which may happen unpredictably.<br />

Preemption in sensor-node programs can best be compared to exception<br />

handling of object-oriented languages. In object-oriented programming, an<br />

exception may unroll a hierarchy of nested methods calls to trigger an exception<br />

handler on a higher hierarchy level. Likewise, in phase-based sensor-node programs,<br />

an event may preempt a hierarchy of nested phases (i.e., sub-phases) and<br />

trigger a phase on a higher hierarchy level.<br />

4.3 Extending the Event <strong>Model</strong>: a <strong>State</strong>-<strong>Based</strong><br />

Approach<br />

In this chapter we have laid down why the event-driven model is inadequate<br />

to model and specify real-world sensor-node applications. The fundamental<br />

reasons <strong>for</strong> this inability are manual state and manual stack management, that<br />

is, the need <strong>for</strong> programmers to meticulously specify the program’s control flow<br />

and memory management. However, to be suitable <strong>for</strong> real-world application<br />

programming, a sensor-node programming model must be expressive enough<br />

to easily and concisely specify the anatomy of sensor-node programs, requiring<br />

neither manual state management, nor manual stack management.


80 Chapter 4. Event-driven <strong>Programming</strong> in Practice<br />

4.3.1 Automatic <strong>State</strong> Management<br />

First off all, to avoid manual state management, a sensor-node programming<br />

model requires an explicit abstraction of what we have presented as phases: a<br />

common context in which multiple actions can per<strong>for</strong>m. Furthermore it requires<br />

mechanisms to compose phases sequentially, hierarchically, and concurrently.<br />

A natural abstraction of program phases are states of finite state machines<br />

(FSM). Finite state machines—in their various representations—have been extensively<br />

used <strong>for</strong> modeling and programming reactive embedded systems. Reactive<br />

embedded systems share many of the characteristics and requirements of<br />

sensor nodes, such as resource efficiency and complex program anatomies.<br />

Particularly the <strong>State</strong>charts model and its many descendants have seen<br />

widespread use. The <strong>State</strong>charts model combines elements <strong>for</strong>m the eventdriven<br />

programming model (events and associated actions) as well as from finite<br />

state machines (states and transitions among states). To these elements it<br />

adds state hierarchy and concurrency. As such, the <strong>State</strong>charts model is well<br />

suited to describe sensor-node programs. <strong>State</strong>charts can help to solve the manual<br />

state-management issues. Particularly, it allows to specify the program’s<br />

control flow in terms of the model’s abstractions (namely transitions between<br />

possibly nested and concurrent states), rather then requiring the programmer to<br />

specify the control flow programmatically. Indeed, we have already used the<br />

graphical <strong>State</strong>chart notation to illustrate the flow of control through phases in<br />

the examples throughout this chapter. The <strong>State</strong>chart model builds the foundation<br />

<strong>for</strong> our sensor-node programming model, which we will present in the next<br />

chapter.<br />

4.3.2 Automatic Stack Management<br />

However, neither the <strong>State</strong>charts model nor any of its descendants do solve the<br />

issue of manual stack management. As we have detailed previously, phases of<br />

sensor-node programs have associated data state. <strong>State</strong>charts and <strong>State</strong>chartlike<br />

models typically do not provide an abstraction <strong>for</strong> such data state. The few<br />

models which do, however, lack a mechanism to manage such state automatically,<br />

that is, to initialize the data state upon state entry (e.g., by allocating and<br />

initializing a state specific variable) and to release it upon exit. The same holds<br />

<strong>for</strong> all (de)initializations related to phases, such as setting up or shutting down<br />

timers, switching on or off radios and sensors, etc. In general, <strong>State</strong>chart and<br />

descendant programming models do not support the (de)initialization of states.<br />

To fix these issues, we propose to make two important additions to the basic<br />

<strong>State</strong>chart model: Firstly, states have dedicated initialization and deinitialization<br />

functions. These functions are modeled as regular actions. They are associated<br />

with both a state and a transition, and are executed within the context of the<br />

associated state. Secondly, states can have associated data state. This state can<br />

be considered as the local variables of states. The lifetime and scope of state<br />

variables are bound to their state. <strong>State</strong> variables are an application of general<br />

(de)initialization functions: data state is allocated and initialized in the state’s<br />

initialization function and released upon the state’s exit in its deinitialization<br />

function. (In our proposed model however, initialization and release of state<br />

variables is transparent to the programmer.) However, state variables are more


4.3. Extending the Event <strong>Model</strong>: a <strong>State</strong>-<strong>Based</strong> Approach 81<br />

than semantic sugar: modeling data state with the explicit state-variable abstraction<br />

allows compile-time analysis of the program’s memory requirements and<br />

only requires static memory management <strong>for</strong> their implementation in the system<br />

software—two invaluable features <strong>for</strong> sensor-node programing.


82 Chapter 4. Event-driven <strong>Programming</strong> in Practice


5 The Object-<strong>State</strong> <strong>Model</strong><br />

In the previous chapter we have described the issues of the event-driven programming<br />

model, namely manual state management and manual stack management.<br />

These issues often lead to memory inefficient, unstructured, and unmodular<br />

programs. Also, we have argued that the way programmers think about<br />

their applications (what we might call the mind model of an application) does<br />

not translate very well into the event-driven programming model. Concretely,<br />

we have diagnosed the lack of a programming abstraction <strong>for</strong> program phases,<br />

which allows to structure the program’s behavior and data state along the time<br />

domain.<br />

In this chapter we will present the OSM state-based model, which we have<br />

designed in order to attack these problems. OSM allows to specify sensor-node<br />

programs as state machines, where program phases are modeled as machine<br />

states. OSM is not a complete departure from the event-driven programming<br />

model. Rather, OSM shares many features of the event-driven model. Just as<br />

in the event-driven model, in the OSM model progress is made only on the occurrences<br />

of events. Likewise, computational actions in OSM are specified as<br />

actions in a sequential programming language, such as C. And just as in the<br />

event-driven model, OSM actions run to completion.<br />

We like to see OSM as an extension of the event-driven model with statemachine<br />

concepts. The first of the two main advancements of OSM over the<br />

event-driven model is that it allows to specify the control flow of an application<br />

on a higher abstraction level, saving the programmer from manual state<br />

management. Through the explicit notion of states, the association of events<br />

to actions is no longer static. Rather, the program’s current machine state defines<br />

the context in which an action is executed. As a result, OSM programs can<br />

be specified in a more modular and well-structured manner. The second advancement<br />

of OSM are state variables, which save programmers from manually<br />

managing temporary data state (i.e., manual stack management). <strong>State</strong> variables<br />

allow to attach data state to explicitly-modeled program states. As the scope and<br />

lifetime of state variables is limited to a state (and its substates), the OSM runtime<br />

environment is able to automatically reclaim the variable’s memory upon<br />

leaving the state. As a result, OSM programs are generally more memory efficient<br />

than conventional event-driven programs. In the following subsections<br />

we will introduce OSM and in<strong>for</strong>mally define its execution model. We have already<br />

presented a preliminary version of OSM in [74]. The version presented<br />

here has undergone some refinements and minor extensions. In Sect. 5.1 we will<br />

present well-known state machine concepts on which OSM is built. In Sect. 5.2<br />

we present the basic <strong>for</strong>m of OSM state machines. We explain how the real-time<br />

requirements of sensor-node programs map to the discrete time model of state<br />

machines in Sect. 5.3. In Sect. 5.4 and Sect. 5.5, respectively, we will present<br />

hierarchy and concurrency as a more advanced means to structure OSM state


84 Chapter 5. The Object-<strong>State</strong> <strong>Model</strong><br />

machines. <strong>State</strong> variables are explained in Sect. 5.6. Finally, in Sect. 5.7 we<br />

summarize the chapter.<br />

5.1 Basic <strong>State</strong>chart Concepts<br />

The semantics and representation (i.e., language) of OSM are based on finite<br />

state machines (FSMs) as well as concepts introduced by <strong>State</strong>charts [53] and its<br />

descendants. Hence, be<strong>for</strong>e discussing OSM in the next sections of this chapter,<br />

we briefly review these concepts this section.<br />

Finite state machines are based on the concepts of states, events, and transitions.<br />

A FSM consists of a set of states and a set of transitions, each of which is a<br />

directed edge between two states, originating <strong>for</strong>m the source state and directed<br />

towards the target state. A machine can only be in one state at a time. Transitions<br />

specify how the machine can proceed <strong>for</strong>m one state to another. Each transition<br />

has an associated event. The transition is taken (it “fires”) when the machine<br />

is in the transition’s source state and its associated event occurs. FSMs can be<br />

thought of as directed, possibly cyclic graphs, with nodes denoting states and<br />

edges denoting transitions.<br />

<strong>State</strong>charts enhance basic FSMs with abstractions in order to model and specify<br />

reactive computer programs. As in event-based programming, <strong>State</strong>charts<br />

introduce actions to finite state machines in order to specify computational<br />

(re)actions. Conceptually, actions are associated either with transitions or with<br />

states. For the definition (i.e., implementation) of actions, most programming<br />

frameworks based on <strong>State</strong>chart models rely on a host language, such as C. Even<br />

though a program could be fully specified with those four concepts explained<br />

above, [53] argues that the representation of complex programs specified in this<br />

naive fashion suffers from state explosion. To alleviate this problem, <strong>State</strong>charts<br />

introduce hierarchy and concurrency to finite state machines. A state can subsume<br />

an entire state machine, whose states are called substates of the composing state.<br />

The composing state is called superstate. This mechanism can be applied recursively:<br />

superstates may themselves be substates of a higher-level superstate. The<br />

superstate can be seen as an abstraction of the contained state machine (bottomup<br />

view). The state machine contained in the superstate can also be seen as a<br />

refinement of the superstate (top-down view). Though a superstate may contain<br />

an entire hierarchy of state machines, it can be used like a regular (i.e., uncomposed)<br />

state in any state machine. Finally, two or more state machines can be<br />

run in parallel, yet communicate through events. <strong>State</strong> machines that contain<br />

neither hierarchy nor concurrency are called flat.<br />

A concept which has received little attention so far are state variables, which<br />

hold data that is local to a state or state hierarchy. Most state-based models do<br />

not provide an abstraction <strong>for</strong> data state but instead rely entirely on variables<br />

of their programming framework’s host language. In the few other models that<br />

encompass variables, the variable scope and lifetime is global to an entire state<br />

machine.<br />

OSM is based on the conceptual elements presented in the previous section,<br />

namely states, events, actions, and state variables. However, OSM differs significantly<br />

from <strong>State</strong>charts and its derivates in the semantic of state variables as


5.2. Flat OSM <strong>State</strong> Machines 85<br />

well as actions. These semantics are fundamental <strong>for</strong> mitigating the problems<br />

of the event-driven model <strong>for</strong> sensor-node programming. There<strong>for</strong>e, applying<br />

existing state-based models to the domain of sensor networks would not be sufficient.<br />

5.2 Flat OSM <strong>State</strong> Machines<br />

Fig. 5.1 illustrates a flat state machine in OSM. In practice, OSM state machines<br />

are specified using a textual language, which we will present in Chap. 6. Here<br />

we will use a graphical notation based on the graphical <strong>State</strong>chart notation <strong>for</strong><br />

clarity.<br />

inA()<br />

A<br />

outA() inB()<br />

B<br />

outB() inC()<br />

int varA e int varB<br />

f<br />

Figure 5.1: A flat state machine.<br />

C<br />

int varC<br />

The sample OSM state machine depicted in Fig. 5.1 consists of three states A,<br />

B, and C. In the graphical notation, states are denoted by rounded rectangles.<br />

The initial state—the state in which the execution of the state machine begins—<br />

is denoted by an arrow sourced in a small circle. In the example, A is the initial<br />

state.<br />

5.2.1 <strong>State</strong> Variables<br />

To each of these states, an integer variable varA, varB, and varC is attached,<br />

respectively. These so-called state variables are a distinct feature in OSM. The<br />

scope of state variables is confined to their associated state (and all its substates,<br />

if any, see Sect. 5.6). OSM supports both primitive data types (e.g., integer types,<br />

characters) as well as structured data types (e.g., arrays, strings, records) in the<br />

style of existing typed programming languages, such as C.<br />

5.2.2 Transitions<br />

In the example, transitions exist between states A and B (triggered by the occurrence<br />

of event e) and between states B and C (triggered by the occurrence<br />

of event f). In OSM, events may also carry additional parameters, <strong>for</strong> example,<br />

sensor values (not depicted here). Each event parameter is assigned a type and<br />

a name by the programmer. Actions can refer to the value of an event parameter<br />

by its name. Typically, a transition is triggered by the event that the transition is<br />

labeled with. Additionally OSM allows to guard transitions by a predicate over<br />

event parameters as well as over the variables in the scope of the source state.<br />

The transition then only fires if the predicate holds (i.e., evaluates to true) on the<br />

occurrence of the trigger event.


86 Chapter 5. The Object-<strong>State</strong> <strong>Model</strong><br />

5.2.3 Actions<br />

In the sample state machine each transition between two states has two associated<br />

actions. The transition between A and B, <strong>for</strong> example, is tagged with two<br />

actions outA() and inB(). In OSM, each action is not only associated with a transition<br />

or a state (as in regular <strong>State</strong>chart models) but with both, a transition and<br />

with either the source or the target state of that transition. Actions can access the<br />

variables in the scope of their associated state. Actions associated with a transition’s<br />

source state are called exit actions (such as outA() in the example) while<br />

actions associated with the target state are called entry actions. Entry actions can<br />

be used to initialize the data state (i.e., state variables) and other (hardware) resources<br />

of the program phase that is modeled by the target state. Entry, exit, or<br />

both actions, as well as their parameters could also be omitted.<br />

Let us now consider the transition between A and B in more detail, which is<br />

tagged with two actions outA() and inB(). When the transition fires, first outA()<br />

is executed and can access e and variables in the scope of state A. Then, after<br />

outA() has completed, inB() is executed and can access e and variables in the<br />

scope of state B (i.e., varB). If source and target states of a transition share a<br />

common superstate, its variables can be accessed in both actions.<br />

Initial states can also declare entry actions, such as inA() of state A in the<br />

example. Such actions are executed as soon as the machine starts. Since there is<br />

no event associated with the entrance of the initial state, inA() can only access<br />

the state variable of A, that is, varA.<br />

5.3 Progress of Time: Machine Steps<br />

Finite state machines imply a discrete time model, where time progresses from<br />

t to t + 1 when a state machine makes a transition from one state to another.<br />

Consider Fig. 5.2 <strong>for</strong> an example execution of the state machine from Fig. 5.1. At<br />

time t = 0, the state machine is in state A and the variable varA exists. Then,<br />

the occurrence of event e triggers a state transition. The action outA() is still<br />

per<strong>for</strong>med at t = 0, then time progresses to t = 1. The state machine has then<br />

moved to state B, variable varB exists and the action inB() is executed. When<br />

event f occurs, outB() is per<strong>for</strong>med in state B, be<strong>for</strong>e time progresses to t = 2.<br />

The state machine has then moved to state C, variable varC exists and the action<br />

inC() is executed. The discrete time of OSM state machines is associated with<br />

the occupancy of states.<br />

in state A<br />

varA exists<br />

execute inA()<br />

execute outA()<br />

in state B<br />

varB exists<br />

execute inB()<br />

execute outB()<br />

in state C<br />

varC exists<br />

execute inC()<br />

0 1 2 discrete time<br />

Figure 5.2: Discrete time.<br />

Approaching time from the perspective of a sensor network that is embedded<br />

into the physical world, a real-time model seems more appropriate. For


5.3. Progress of Time: Machine Steps 87<br />

start<br />

e f<br />

inA() outA() inB() outB() inC()<br />

0<br />

state A<br />

lifetime varA<br />

state B<br />

state C<br />

lifetime varB lifetime varC<br />

1<br />

Figure 5.3: Mapping real-time to discrete time.<br />

2<br />

real time<br />

discrete time<br />

example, sensor events can occur at virtually any point in time, and actions and<br />

transitions require a finite amount of real-time to execute. Hence, it becomes<br />

also important in which order actions are executed. Essentially, we are faced<br />

with the issue of interfacing the discrete time model of state machines with the<br />

real-time model of sensor networks. We will discuss this interface in this and<br />

the subsequent sections.<br />

Inspired by traditional event-based programming models, actions are considered<br />

atomic entities that run to completion without being interrupted by the<br />

execution of any other action. Fig. 5.3 illustrates the mapping of real-time to discrete<br />

time, the respective states and variable lifetimes. Since the execution of actions<br />

consumes a non-zero amount of real time, events may arrive in the system<br />

while some action is currently being executed. Those events cannot be handled<br />

immediately and must be stored temporarily. There<strong>for</strong>e, arriving events are always<br />

inserted into a FIFO queue using the queue’s enqueue operation. Whenever<br />

the system is ready to make the next transitions, that is, when all exit and entry<br />

actions have been completed, the system retrieves the next event from the queue<br />

(using the dequeue operation) in order to determine the next transition.<br />

While state transitions are instantaneous in the discrete time model, they consume<br />

a non-zero amount of real time. The entire process of making a state transition<br />

is called a state-machine step. A step involves dequeuing the next event,<br />

determining the resulting transition(s), executing the exit action(s) of the current<br />

state(s), and executing the entry action(s) of the subsequent state(s). Also,<br />

in actions, events may be emitted programmatically, which are inserted into the<br />

event queue. A step always results in the progression of discrete time. Except<br />

<strong>for</strong> self-transitions, steps always lead to state changes. If the event queue still<br />

holds events after completing a step, the next step starts immediately. If, however,<br />

the event queue is empty after competing a step, the system enters sleep<br />

mode until the occurrence of the next event.<br />

5.3.1 Non-determinism<br />

OSM state machines may exhibit non-deterministic behavior, a characteristic is<br />

shares with other event-based systems. The reason <strong>for</strong> this non-determinism<br />

is that the exact sequence of steps per<strong>for</strong>med not only depends on the reception<br />

order of input events but also on their timings and on the duration of per<strong>for</strong>ming<br />

a step. The amount of real-time consumed by a step depends on three factors:


88 Chapter 5. The Object-<strong>State</strong> <strong>Model</strong><br />

the implementation of the underlying system software (<strong>for</strong> event handling and<br />

determining transitions), the actions involved in the step (which may require<br />

more or less computation), and the speed of the executing processor.<br />

A inB() B<br />

t1<br />

Figure 5.4: <strong>State</strong> machines that programmatically emit events may exhibit nondeterministic<br />

behavior. The event e, which is emitted by inB(), may<br />

be inserted be<strong>for</strong>e or after an event t2 depending on the execution<br />

speed of the action.<br />

start<br />

start<br />

0<br />

state A state B state C<br />

state A<br />

t1 e<br />

inB()<br />

inB()<br />

1<br />

t2<br />

t1 t2<br />

state B<br />

e<br />

t2<br />

e<br />

2<br />

C<br />

D<br />

state D<br />

real time<br />

discrete time<br />

real time<br />

Figure 5.5: Non-determinism in OSM: the order of state transitions (and thus<br />

the invocation order of actions) may depend on the execution speed<br />

of (previous) actions. The top part of the figure depicts a possible run<br />

of the OSM state machine in Fig. 5.4 on a fast processor, the bottom<br />

part on a fast processor.<br />

Consider, <strong>for</strong> example, two runs of the state machine depicted in Fig. 5.4. The<br />

machine has a single computational action inB(), which programmatically emits<br />

an event e. Let us now consider two runs of that state machine on sensor nodes<br />

having processors with different speeds. In both runs two input events (t1 and<br />

t2) are triggered externally, exactly t1 and t2 time units from the start of the machine.<br />

The top part of Fig. 5.5 depicts a possible result on the faster processor,<br />

while the bottom part of the figure depicts a possible result on the slower processor.<br />

On the faster processor, inB() terminates earlier and e is inserted into the<br />

queue be<strong>for</strong>e the occurrence of t2, whereas on the slower processor inB() only<br />

terminates after the occurrence of t2 and thus t2 is inserted first. Thus, both runs<br />

end up in different states. <strong>State</strong> C is reached on the fast processor and state D is<br />

reached on the slow processor.


5.3. Progress of Time: Machine Steps 89<br />

Note that OSM’s nondeterminism does not stem from its transitions semantics,<br />

which are unambiguous. Rather, nondeterminism is introduced by race<br />

conditions when programmatically emitting events. Such events may be inserted<br />

into the event queue be<strong>for</strong>e or after a externally-triggered event, depending<br />

on the processor’s execution speed. Also note that event-driven programs<br />

exhibit non-deterministic behavior <strong>for</strong> the same reason.<br />

A possible solution to this kind of non-determinism would be to always insert<br />

programmatically-emitted events at the beginning rather than the end of the<br />

event queue. This way there would be no race conditions between internally<br />

and externally emitted events. However, this behavior is in conflict not only<br />

with the event-driven model, where events are often use to trigger some lowpriority<br />

action at a later time. It may also be in conflict with the synchronous<br />

execution model. In the synchronous model, where the output from the system<br />

is always synchronous to its input, the programmatically-emitted event as well<br />

as the original input event (events e and t1 in the example, respectively) would<br />

have to be concurrent. This could to a situation where the original transition<br />

would have not been triggered, namely if the source state had a higher-priority<br />

transition triggered by the programmatically emitted event.<br />

5.3.2 Processing <strong>State</strong> Changes<br />

A principal problem of all programs dealing with external inputs is that the<br />

internal program state may not represent the state of the environment correctly<br />

at all times. That is because real-world events require some time until they are<br />

propagated through the system and are reflected in the system’s internal state.<br />

This is particularly true when events are propagated through network messages<br />

or are being generated from sensor input through multiple stages of processing.<br />

This is a principle problem and applies to the event-driven, the state-based, and<br />

the multi-threaded programming model alike.<br />

Consider the case where the program is writing to an already closed network<br />

connection because the disconnect event has not been processed (<strong>for</strong> example,<br />

the network packet containing the disconnect request has not completely received<br />

or the disconnect event is lingering in the queue). In this case the program<br />

would still consider the connection to be open and may try to send the data over<br />

already closed connection.<br />

The actual problem is that programmers must handle those cases, which typically<br />

result in errors. As these cases typically occur only rarely, they are hard to<br />

test systematically and may be <strong>for</strong>gotten entirely during program design. From<br />

our experience, error handling of these cases can become very complex. However,<br />

if they occur and error handling fails, the node may crash or enter some<br />

undefined state.<br />

5.3.3 No Real-Time Semantics<br />

The OSM model is not a real-time model—it does not allow to specify deadlines<br />

or analyze execution times. It neither has means to quantify execution times nor<br />

can they be specified by the programmer of OSM state machines.


90 Chapter 5. The Object-<strong>State</strong> <strong>Model</strong><br />

5.4 Parallel Composition<br />

An important element of OSM is the support <strong>for</strong> parallel state machines. In a<br />

parallel composition of multiple flat state machines, multiple states are active at<br />

the same time, exactly one <strong>for</strong> every parallel machine. Let us consider an example<br />

of two parallel state machines, as depicted in Fig. 5.6. (This parallel machine<br />

is the flat state machine from Fig. 5.1 composed in parallel with a copy of the<br />

same machine where state and variable names are primed.) At every discrete<br />

time, this state machine can assume one out of 9 possible state constellations<br />

with two active states each—one <strong>for</strong> each parallel machine. These constellations<br />

are A|A ′ , A|B ′ , A|C ′ , B|A ′ , B|B and so <strong>for</strong>th, where A|A ′ is the initial state constellation.<br />

For simplicity we sometimes say A|A ′ is the initial state.<br />

Parallel state machines can handle events originating from independent<br />

sources. For example, independently tracked targets could be handled by parallel<br />

state machines. While events are typically triggered by real-world phenomena<br />

(e.g., a tracked object appears or disappears), events may also be emitted by<br />

the actions of a state machine to support loosely-coupled cooperation of parallel<br />

state machines.<br />

Besides this loose coupling, parallel state machines can be synchronized in the<br />

sense that state transitions of concurrent machines can occur concurrently in the<br />

discrete time model. In the real-time model, however, the state transitions and<br />

associated actions are per<strong>for</strong>med sequentially.<br />

A<br />

outA() inB()<br />

B<br />

outB() inC()<br />

int varA e int varB<br />

f<br />

A’<br />

outA’() inB’()<br />

B’<br />

outB’() inC’()<br />

int varA’ e int varB’<br />

f<br />

C<br />

int varC<br />

C’<br />

int varC’<br />

Figure 5.6: A parallel composition of two flat state machines.<br />

The parallel state machines of Fig. 5.6 are synchronized through events e and<br />

f, which trigger transitions in both concurrent machines. Fig. 5.7 shows the<br />

mapping of real-time to discrete time at discrete time t = 1. After discrete time<br />

has progressed to t = 1 on the occurrence of e, all entry actions are executed in<br />

any order. When event f arrives, all exit actions are per<strong>for</strong>med in any order.<br />

5.4.1 Concurrent Events<br />

Up to now, each event has been considered independently. However, in the<br />

discrete time model, events can occur concurrently when concurrent machines<br />

programmatically emit events in a synchronized step. For example, when parallel<br />

state machines emit events in exit actions when simultaneously progressing<br />

from time t = 1 to t = 2 (see Fig. 5.7), the emitted events have no canonical order.<br />

The event queue should preserve such unordered sets of concurrent events,<br />

rather than imposing an artificial total ordering on concurrent events by inserting<br />

them one after another into the queue. For this purpose, the event queue


5.5. Hierarchical Composition 91<br />

state B, lifetime varB<br />

state B’, lifetime varB’<br />

inB() inB’() outB()<br />

f<br />

outB’()<br />

real time<br />

0 1<br />

2 discrete time<br />

Figure 5.7: Possible mapping of real-time to discrete time <strong>for</strong> parallel machines.<br />

of OSM operates on sets of concurrent events rather than on individual events.<br />

All elements of a set are handled in the same discrete state-machine step. The<br />

enqueue operation takes a set of concurrent events as a parameter and appends<br />

this set as a whole to the end of the queue. The dequeue operation removes the<br />

set of concurrent events from the queue that has been inserted earliest. Whenever<br />

the system is ready to make a transition, it uses the dequeue operation to<br />

determine the one or more events to trigger transitions.<br />

All events emitted programmatically in exit and entry actions, respectively,<br />

are enqueued as separate sets of events, even though they are emitted during<br />

the same state machine step. That is, each state-machine step consumes one set<br />

of events, and may generate up to two new sets: one <strong>for</strong> events emitted in exit<br />

actions and another set <strong>for</strong> events emitted in entry actions. Clearly, the arrival<br />

rate of event sets must not be larger than the actual service rate, which is the<br />

responsibility of the programmer.<br />

5.4.2 Progress in Parallel Machines<br />

The execution semantics of a set of parallel state machines can now be described<br />

as follows. At the beginning of each state-machine step, the earliest set of concurrent<br />

events is dequeued unless the queue is empty. Each of the parallel state<br />

machines considers the set of dequeued (concurrent) events separately to derive<br />

a set of possible transitions. Individual events may also trigger transitions<br />

in multiple state machines. Similarly, if multiple concurrent events are available,<br />

multiple transitions could fire in a single state machine. To resolve such<br />

ambiguous cases, priorities must be assigned to transitions. Only the transition<br />

with the highest priority is triggered in each state machine. Actions are executed<br />

as described in Sect. 5.3. The dequeued set of concurrent events is then dropped.<br />

5.5 Hierarchical Composition<br />

Another important abstraction supported by OSM are state hierarchies, where<br />

a single state (i.e., a superstate) is further refined by embedding another state,<br />

state machine (as depicted in Fig. 5.8 (a)) or multiple, concurrent state machines<br />

(as depicted in Fig. 5.8 (b)). Just as uncomposed states, superstates can have<br />

state variables, can be source or target of a transition, and can have entry and


92 Chapter 5. The Object-<strong>State</strong> <strong>Model</strong><br />

exit actions defined. Note, however, that unlike <strong>State</strong>charts, in OSM transitions<br />

may not cross hierarchy levels. (Although transitions across hierarchy levels<br />

may give the programmer more freedom in the specification of actual programs,<br />

we believe that this feature impairs modularity.)<br />

The lifetime of embedded state machines starts in their initial state when their<br />

superstate is entered. The lifetime of embedded state machines ends when their<br />

superstate is left through a transition.<br />

The lifetime of state variables attached to a superstate is bound to the lifetime<br />

of that superstate. The scope of those variables recursively extends to<br />

all substates. In other words, an action defined in a state can access the variables<br />

defined in that state plus the variables defined in all of its superstates.<br />

<strong>State</strong> variables in hierarchically-composed state machines are further discussed<br />

in Sect. 5.6.<br />

As in parallel state machines, in hierarchical state-machines multiple states<br />

are active at the same time. The constellation of active states contains the hierarchy’s<br />

superstate plus a single state or state constellation (in the case of parallel<br />

machines) on every hierarchy level. The active constellation of a state machine<br />

can be represented as a tree, either graphically or textually. For example, starting<br />

from the initial state A, the state machine depicted in Fig. 5.8 (a) assumes the<br />

state constellation B(D) after receiving event e. In B(D), both states B and D<br />

are active and D is the substate of B. The state machine depicted in Fig. 5.8 (b)<br />

assumes the state constellation B(D|F ) in the same situation.<br />

A<br />

C<br />

e<br />

f<br />

B<br />

g<br />

D<br />

E<br />

(a) A flat state machine embedded<br />

into state B<br />

5.5.1 Superstate Entry<br />

g<br />

Figure 5.8: Hierarchies of state machines<br />

A<br />

C<br />

e<br />

f<br />

B<br />

g<br />

D F<br />

E<br />

g<br />

h h<br />

(b) A parallel state machine embedded<br />

into state B<br />

When a superstate is entered (either through a transition or at the start of the<br />

machine), also the initial state of the embedded state machine is entered in the<br />

same atomic step. If the initial state of the embedded state machine is refined<br />

further, this applies recursively. Fig. 5.9 illustrates the mapping of real-time to<br />

discrete time of the state machine depicted in Fig. 5.8 (b) while progressing from<br />

G


5.5. Hierarchical Composition 93<br />

the initial state A to the state constellation B(D|F ). For this example, assume<br />

that each transition has both an exit and entry action named outS() and inT (),<br />

respectively, where S is a placeholder <strong>for</strong> the transition’s source state and T is a<br />

placeholder <strong>for</strong> the transition’s target state. On the occurrence of event e, the exit<br />

action of state A is executed, still at discrete time t = 0. Then the discrete time<br />

advances to t = 1 and the state constellation B(D|F ) is entered. With becoming<br />

active, the entry action of each state of the active constellation is executed. Entry<br />

actions of states higher in the hierarchy are invoked be<strong>for</strong>e those of states lower<br />

in the hierarchy. Entry actions of states on the same hierarchy level (that is, entry<br />

actions of states of parallel machines) are executed in any order, as described in<br />

Sect. 5.4.2.<br />

start e<br />

A B<br />

inA() outA() inB() inD()<br />

0 1<br />

inF()<br />

D<br />

F<br />

real time<br />

discrete time<br />

Figure 5.9: Mapping of real-time to discrete time when entering the constellation<br />

B(D|F ) of the state machine depicted in Fig. 5.8 (b) from state A.<br />

5.5.2 Initial <strong>State</strong> Selection<br />

Up to now we have only considered state machines with a single initial state on<br />

each hierarchy level. However, OSM state machines may have multiple potential<br />

initial states per level, of which exactly one must be selected upon superstate<br />

entry. Which of the potential initial state is actually selected depends on a condition<br />

that can be specified by the programmer. In OSM, these conditions are<br />

modeled as guards. In the graphical version of OSM, these guards have an intuitive<br />

representation: they are placed on the transitions from the small circle to<br />

the initial states. Fig. 5.10 illustrates the conditional entry to one of the substates<br />

D and E of superstate B. Which of B’s substates is actually entered depends<br />

on the value of a. If a = 1, substate D is entered, E otherwise. Note that it is<br />

not possible to prevent or delay substate entry. If a superstate is entered, exactly<br />

one of its substates must be entered. There<strong>for</strong>e the guard conditions must be<br />

discrete.<br />

As regular guards are initial state conditions, they may be composed of expressions<br />

over state variables in scope, (side-effect free) computational functions,<br />

or both. However, they may not refer to any in<strong>for</strong>mation regarding the<br />

transition that lead to the entry of the superstate (such as source state, event<br />

type, and event parameter). This may seem awkward. However, conditional<br />

state entry based on such in<strong>for</strong>mation can be easily implemented by setting state<br />

variables in entry actions. When B is entered, the state variable a can be set in


94 Chapter 5. The Object-<strong>State</strong> <strong>Model</strong><br />

the entry actions of B and then be evaluated in the guard, selecting the actual<br />

initial state. This is possible since the entry actions of a superstate are always<br />

executed be<strong>for</strong>e the initial-state condition is evaluated.<br />

A<br />

g<br />

C<br />

e<br />

f<br />

e<br />

f<br />

inB1()<br />

inB2()<br />

inB3()<br />

inB4()<br />

B<br />

var int a<br />

[a=1]<br />

[else]<br />

Figure 5.10: Conditional entrance to the substates D and E of B, depending on<br />

guards over the state variable a. The state variable a can be set in<br />

the entry actions of B, <strong>for</strong> example, depending on which transition<br />

was taken.<br />

5.5.3 Substate Preemption<br />

When a transition fires, it ends the lifetime of its source state. We say the transition<br />

terminates the source state normally. If the source state of a transition is<br />

a superstate, the transition also preempts its substates (i.e., the embedded state<br />

machine). Then we say the transition preempts the source state’s substates. <strong>State</strong>s<br />

can only be left either through normal termination or through preemption. Substates<br />

are preempted level-by-level, starting from the bottom-most state in the<br />

hierarchy. A substate cannot prevent its superstate from being left (and thus<br />

itself from being preempted).<br />

If a state is preempted, its exit actions do not fire as during normal termination.<br />

In order to allow the preempted state to react to the termination or preemption<br />

of its superstate (and thus its own preemption), preemption actions are<br />

introduced. Preemption actions are useful, <strong>for</strong> example, to release resources that<br />

where initialized in the preempted states’ entry actions. Each state can have a<br />

single preemption action. This preemption action is invoked as the state is preempted.<br />

Preemption actions are invoked in the order in which their states are<br />

preempted, that is, from the bottom of the hierarchy to its top. The substates’<br />

preemption actions are invoked be<strong>for</strong>e the exit action of their superstate. Preemption<br />

actions of parallel states are executed in any order.<br />

The scope of the preemption action is the to-be-preempted substate. That is, a<br />

preemption action can access all state variables its state be<strong>for</strong>e it is actually being<br />

preempted plus all state variables of (direct and indirect) superstates. Note<br />

however, that a transition terminating a state directly does not trigger that state’s<br />

preemption action—any required operations can be per<strong>for</strong>med in the regular<br />

exit action. In a preemption action, there is no means to determine any in<strong>for</strong>mation<br />

of the actual transition causing the preemption, such as its trigger and its<br />

source state.<br />

D<br />

E


5.5. Hierarchical Composition 95<br />

Let us again consider the state machine depicted in Fig. 5.8 (b), assuming every<br />

transition has both an exit and entry action named outS() and inT (), respectively,<br />

where S is a placeholder <strong>for</strong> the transition’s source state and T is a<br />

placeholder <strong>for</strong> the transition’s target state. Let us also consider that each state<br />

P has a preemption actions preP (). Fig. 5.11 illustrates the preemption of the<br />

constellation B(D|G) through the transition from B to C triggered by f.<br />

B<br />

D<br />

G<br />

t<br />

f<br />

prG() prD()<br />

outB() inC()<br />

C<br />

t+1<br />

real time<br />

discrete time<br />

Figure 5.11: Mapping of real-time to discrete time when leaving the constellation<br />

B(D|G) of the state machine depicted in Fig. 5.8 (b) to state C.<br />

5.5.4 Progress in <strong>State</strong> Hierarchies<br />

We can now describe the full execution semantics of a step in hierarchical and<br />

concurrent OSM state machines. Note however, that the algorithm presented<br />

here is not actually used in the implementation of OSM. It is only presented<br />

<strong>for</strong> the understanding of the reader. The actual implementation of OSM state<br />

machines is described in Sect. 6.2 of the next chapter.<br />

At the beginning of each state-machine step, the earliest set of concurrent<br />

events is dequeued. Then the set of dequeued events is considered to derive<br />

possible transitions by per<strong>for</strong>ming a preorder search on the tree representing<br />

the active constellation, beginning from the root of the tree. If a matching transition<br />

is found in a state, the transition is marked <strong>for</strong> later execution and the<br />

search of the state’s sub-trees (if any) are skipped. If multiple transitions could<br />

fire in a single state, only the transition with the highest priority is marked, as<br />

described in Sect. 5.4.2. Skipping subtrees after finding a match means that transitions<br />

higher in the state hierarchy take precedence over transitions lower in<br />

the hierarchy. The search continues until the entire tree has been traversed (or<br />

skipped) and all matching transitions have been marked.<br />

Then the following steps are per<strong>for</strong>med while the state machine still is in the<br />

source constellation. For each transition selected <strong>for</strong> execution, the preemption<br />

actions of states preempted by that transition are invoked in the correct order<br />

(from bottom to top). This is achieved by per<strong>for</strong>ming a postorder traversal on<br />

each of the subtree of the transition’s source state, executing preemption actions<br />

as states are visited. The order in which entire subtrees are processed can be<br />

chosen arbitrarily, since each subtree represents a parallel machine. While still<br />

being in the source constellation, the exit actions of all marked transitions are


96 Chapter 5. The Object-<strong>State</strong> <strong>Model</strong><br />

executed. Again, because all marked transitions are always sourced in parallel<br />

states, they have no natural order and may be executed in any order.<br />

Next, the state machine assumes a new active constellation, that is, discrete<br />

time advances from t to t + 1. Now, the entry actions of the target states of the<br />

marked transitions are executed. Again, they can be executed in any order. If<br />

the entered target state, however, is a superstate, the entry actions of its substates<br />

are invoked according to their level in the hierarchy from top to bottom,<br />

as previously described in Sect. 5.5.1. This is achieved by per<strong>for</strong>ming a preorder<br />

traversal of the subtrees sourced in transition targets, executing initial entry actions<br />

as states are visited.<br />

Finally, the dequeued set of events is dropped. If no matching transition was<br />

found, the dequeued set of events is dropped anyhow and the machine remains<br />

in the actual constellation. Events emitted by actions during the state machine<br />

step are enqueued in analogy to Sect. 5.4.2.<br />

Fig. 5.12 (a) depicts a state machine with transitions triggered by a single event<br />

e on multiple levels. Fig. 5.12 (b) depicts the machine’s initial constellation and<br />

the constellation active after per<strong>for</strong>ming the step triggered by event e. The initial<br />

constellation is shown together with the result of the in-order search <strong>for</strong> transitions<br />

triggered by e. <strong>State</strong>s marked with either check marks or crosses are<br />

sources of matching transitions, while a missing mark denotes that there is no<br />

transition matching event e. Transitions actually selected <strong>for</strong> the upcoming machine<br />

step are checked. A cross denotes a state with a matching transition that<br />

is not selected (i.e., it does not fire) because a transition on a higher hierarchy<br />

level takes precedence. (In the example, this is always the case <strong>for</strong> the transition<br />

from D to E, so the transition is redundant. However, such a situation may<br />

make sense, if one or both transitions had guards.) Be<strong>for</strong>e executing the transitions<br />

and their associated actions, first preemption actions are invoked. In the<br />

example, only state D is preempted by the transition from A to B. Thus D’s<br />

preemption action is invoked. Then the exit actions of all checked states are executed<br />

and the target constellation is assumed, as depicted in Fig. 5.12 (b). Note<br />

that all target states (i.e., states entered directly, here B, G, and J) are from parallel<br />

compositions. By entering state J, however, also its substate K is entered.<br />

Entry actions of implicitly entered states are always invoked after their direct<br />

superstate.<br />

5.6 <strong>State</strong> Variables<br />

<strong>State</strong> variables hold in<strong>for</strong>mation that is local to a state and its substates. The<br />

scope of the variables of a state S extends to entry, exit, and preemption actions<br />

that are associated with S and to all actions of states that are recursively<br />

embedded into S via hierarchical composition. In particular, state variables are<br />

intended to store in<strong>for</strong>mation common to all substates and share it among them.<br />

The lifetime of state variables start when their state is entered just be<strong>for</strong>e<br />

invoking the state’s entry actions. Their lifetime ends when the state is left<br />

(through a transition or through preemption) right after the invocation of the<br />

last exit action. With respect to variable lifetimes, there is a special case <strong>for</strong> self<br />

transitions that enter and leave the same state. Here, the variables of the affected


5.7. Summary 97<br />

A<br />

D<br />

E<br />

e<br />

e<br />

B<br />

C<br />

F<br />

e<br />

G<br />

(a)<br />

J<br />

H<br />

e<br />

K<br />

source constellation<br />

(initial state)<br />

Root<br />

A C<br />

D F<br />

H<br />

Root(A(D)|C(F|H))<br />

Root<br />

event e B<br />

C<br />

(b)<br />

target consteallation<br />

(after occurrence of e)<br />

G J<br />

K<br />

Root(B|C(G|J(K)))<br />

Figure 5.12: Determining the transitions to fire in a state machine step. The state<br />

machine depicted in (a) starts in its the initial constellation and e is<br />

the next event from the queue. Transitions originating in A, F , and<br />

H are selected <strong>for</strong> firing. The transition from D is skipped as transitions<br />

higher in the hierarchy take precedence. In a non-concurrent<br />

state machine at most one transition can fire. If multiple transitions<br />

fire, they can do so in any order.<br />

state and their values are retained during the transition, rather then deleting the<br />

variables and creating new instances.<br />

Note that on a particular hierarchy level, a non-concurrent state machine can<br />

only assume one state at a time. Hence, only the variables of this current state<br />

are active. Variables of mutually exclusive states on the same level can then<br />

be allocated to overlapping memory regions in order to optimize memory consumption<br />

(see Sect. 6.1.1).<br />

By embedding a set of parallel machines into a superstate, actions in different<br />

parallel state machines may access a variable of the superstate concurrently<br />

during the same state machine step at the same discrete time. This is no problem<br />

as long as there is at most one concurrent write access. If there are multiple<br />

concurrent write accesses, these accesses may have to be synchronized in some<br />

way. Due to the run-to-completion semantics of actions, a single write access<br />

will always completely execute be<strong>for</strong>e the next write access can occur. However,<br />

the order in which write accesses are executed (in the real-time model) is<br />

arbitrary and may lead to race conditions. Write synchronization is up to the<br />

programmer.<br />

5.7 Summary<br />

In this chapter we have in<strong>for</strong>mally presented OSM, a programming model <strong>for</strong><br />

resource constrained sensor-nodes. OSM is an extension of the event-driven<br />

programming model, adding two main features to the popular programming<br />

model: firstly, an explicit abstraction of program state as well as mechanisms to


98 Chapter 5. The Object-<strong>State</strong> <strong>Model</strong><br />

compose states hierarchically and in parallel. There<strong>for</strong>e, in OSM, programs are<br />

specified as hierarchical and concurrent state machines. OSM allows to specify<br />

a program’s control flow in terms of program states and transitions between<br />

them. The addition of these abstractions in OSM allow to specify sensor-node<br />

programs more modular and structured compared to the conventional eventdriven<br />

model. The second feature builds on the first: OSM uses the explicit<br />

notion of state as scoping and lifetime qualifier <strong>for</strong> variables, as well as context<br />

<strong>for</strong> computational actions. The main achievement of state variables is the automated<br />

reuse of memory <strong>for</strong> storing temporary data by mapping data of mutually<br />

exclusive states to overlapping memory regions.<br />

Despite these additions, OSM shares several features of the event-driven programming<br />

model. As in the event-driven programming model, progress is made<br />

only in reaction to events by the invocation of actions, which also run to completion.<br />

Furthermore, OSM also stores events in an event queue while a previous<br />

event is being processed.


6 Implementation<br />

In the previous chapter we have presented the fundamental concepts and abstractions<br />

behind the Object <strong>State</strong> <strong>Model</strong>. In this chapter we will examine the<br />

four missing pieces that are needed to turn these concepts into a concrete programming<br />

framework, which allows to produce executable sensor-node programs<br />

from OSM specifications. In Sect. 6.1 we will present our programming<br />

language <strong>for</strong> OSM. This language provides the syntactic elements to make use<br />

of the concepts and abstractions found in the OSM programming model (such as<br />

state variables, state transitions, and actions) thus allowing to specify concrete<br />

programs. The OSM language is used only to specify the program’s variables,<br />

structure, and control flow. Computational operations (i.e., the implementation<br />

of actions) are specified as functions of a procedural host language, such as C.<br />

These functions are called from within the OSM-language code. As such, the<br />

OSM language is not a classical programming language.<br />

Be<strong>for</strong>e an executable can be created, an OSM compiler first translates the<br />

OSM-language code into the host language as well. A language mapping specifies<br />

how OSM specifications are translated into the host language. It specifies,<br />

<strong>for</strong> example, how OSM actions are translated into function signatures of the host<br />

language. The language mapping and implementation details of the compiler<br />

are presented in Sect. 6.2 and 6.3, respectively. In the final compilation step an<br />

executable is created <strong>for</strong>m the host-language files by a plat<strong>for</strong>m-specific hostlanguage<br />

compiler. The steps to create an executable from an OSM specification<br />

is depicted in Fig. 6.1.<br />

specified by<br />

programmer<br />

generated variable<br />

code mapping<br />

generated<br />

executable<br />

OSM program structure<br />

language mapping<br />

by OSM compiler<br />

control<br />

structure<br />

host−language compiler<br />

target−specific object code<br />

actions<br />

Key:<br />

OSM language file<br />

host−language file<br />

Figure 6.1: The OSM language is used to specify a program’s structure and control<br />

flow as state machines, and to associate variables to states. Computational<br />

operations are specified in a host language. An OSM compiler<br />

then maps the OSM program specification to this host language<br />

as well, from which the executable is created by a target-specific hostlanguage<br />

compiler.


100 Chapter 6. Implementation<br />

When run on an actual sensor node, the generated executable then relies on<br />

support by the runtime environment. This environment must be provided by<br />

the target plat<strong>for</strong>m’s system software. For OSM-generated executables, the only<br />

runtime support required is a basic event-driven system software capable of<br />

handling sets of events. In Sect. 6.4 we present such a system software. Its<br />

implementation is largely based on our original event-driven BTnode system<br />

software, which we have already presented in Sect. 3.5.1. There<strong>for</strong>e we only<br />

highlight the changes to the original system.<br />

6.1 OSM Specification Language<br />

As opposed to most state machine notations, which are graphical, OSM specifications<br />

use a textual language. The following subsections present important<br />

elements of this language.<br />

6.1.1 <strong>State</strong>s and Flat <strong>State</strong> Machines<br />

The prime construct of the OSM language is a state. The definition of a state includes<br />

the definition of its variables, its entry actions, its substates, its transitions<br />

(which include exit actions), and its preemption actions. Many of those elements<br />

are optional or are useful in hierarchical compositions only. We will explain the<br />

elements of state definitions as well as their use in this and the following subsections.<br />

<strong>State</strong> definitions have the following <strong>for</strong>m (where S is the name of the<br />

state):<br />

state S {<br />

state-variables<br />

entry-actions<br />

substates<br />

transitions<br />

preemption-actions<br />

}<br />

A flat state machine is composed of one or multiple states, of which at least<br />

one must be an initial state. Multiple states of a flat machine must be connected<br />

through transitions, so that every state of the machine is reachable from its initial<br />

state. In the OSM textual language, a state machine composed of multiple states<br />

must be hierarchically contained in a superstate. We will present hierarchy in<br />

Sect. 6.1.2 below. For now we will focus on flat state machines. Prog. 6.1 is an<br />

example of a simple, yet complete OSM program implementing a flat OSM state<br />

machine (lines 3-20). The state machine consists of three states, A, B, and C,<br />

where A is the initial state (denoted by the initial keyword).<br />

Outgoing Transitions and Exit Actions<br />

Definitions of transitions have the following <strong>for</strong>m (where T is the target state, e<br />

is the trigger event, g is the guard, and aexit() is the entry action to be triggered):<br />

[g]e / aexit() -> T ;


6.1. OSM Specification Language 101<br />

Program 6.1: A flat state machine with variables, as well as entry and exit actions<br />

in the OSM textual language.<br />

PROG_1<br />

inA()<br />

A<br />

int a<br />

outA()<br />

e<br />

B<br />

int b<br />

outB()<br />

f e<br />

inC1() inC2()<br />

C<br />

int c<br />

f<br />

outA()<br />

1 state PROG_1 {<br />

2<br />

3 initial state A {<br />

4 var int a;<br />

5 onEntry: start/ inA();<br />

6 e / outA() -> B;<br />

7 f / outA() -> A;<br />

8 } // end state A<br />

9<br />

10 state B {<br />

11 var int b;<br />

12 f / outB() -> C;<br />

13 e / -> C;<br />

14 } // end state B<br />

15<br />

16 state C {<br />

17 var int c;<br />

18 onEntry: B -> f/ inC1();<br />

19 onEntry: B -> e/ inC2();<br />

20 } // end state C<br />

21<br />

22 }<br />

Transitions must be defined in their source state. The definition of a transition<br />

must contain a trigger and, except <strong>for</strong> self-transitions, contains a target state.<br />

The source state of a transition does not need to be specified explicitly. The specification<br />

of a guard and an exit action is optional. Actions are defined outside<br />

of OSM in a host language. In line 6 of Prog. 6.1 a transition is defined leading<br />

from A to B. It is triggered by event e. It has an exit action outA() and has no<br />

guard.<br />

<strong>State</strong> B is source of two transitions declared in lines 12 and 13. Transitions<br />

have priorities according to their order of definition, with the first transition<br />

having the highest priority. For example, if two concurrent events e and f occur<br />

in state B, both transitions could fire. However, only the transition triggered by<br />

f is taken, since it is declared be<strong>for</strong>e the transition triggered by e. In OSM, the<br />

selection of transitions is always deterministic.<br />

Self-transitions are transitions with identical source and target states. In OSM<br />

there are two different ways to specify self-transitions, which have different semantics.<br />

If the target state is given in the specification of a self-transition (as<br />

in “f / outA() -> A;” in line 7), the transition leaves the source state and<br />

immediately re-enters it. That is, all state variables go out of scope and new instances<br />

are created. Also, the state’s exit and entry actions are executed as well<br />

as preemption actions of substates. If, however, the target state is omitted, as in<br />

“f / outA();”, the state variables and their values are retained and only the


102 Chapter 6. Implementation<br />

specified action is executed. If the target state is missing, the specification of an<br />

action is mandatory.<br />

Incoming Transitions and Entry Actions<br />

As stated previously, transitions are defined in the scope of the transition’s<br />

source state together with the transition’s exit action. In order to specify entry<br />

actions, part of the transition must be again declared in the transition’s target<br />

state. That is, if a transition is to trigger both an exit and an entry action,<br />

the transition appears twice in the OSM code: It must be defined in the transition’s<br />

source state, together with the exit action. And it must be declared again<br />

as incoming transition in the target state (together with the entry action). Both<br />

declarations denote the same transition, if source state, target state, trigger and<br />

guard are equal. If only the exit action of a transition is required, its declaration<br />

as incoming transition is obsolete.<br />

The reason <strong>for</strong> this seeming redundancy is the intent to reflect OSM’s scoping<br />

of actions in the programming language. In OSM, exit actions are executed in<br />

the context of a transition’s source state, whereas entry actions are executed in<br />

the context of a transition’s target state. This separation is reflected in the syntax<br />

and clearly fosters modularity, as changes to the incoming action do not require<br />

changes in the transition’s source state (and vice versa) at the price of slightly<br />

more code.<br />

The general <strong>for</strong>m of entry actions is (where S is the source state, e is the trigger<br />

event, g is the guard, and aentry() is the entry action to be triggered):<br />

onEntry: S -> [g]e / aentry();<br />

In Prog. 6.1, a transition from B to C triggered by f has an entry action inC1().<br />

The actual transition is defined in line 12. The corresponding entry action is<br />

declared in transition’s target state C in line 18. The declaration of the entry<br />

action includes the transition’s source state B and its trigger f. However, the<br />

declaration of the transition’s exact source state as well as its trigger event and<br />

guard are optional and only required to identify the exact incoming transition<br />

should there be multiple incoming transitions. This in<strong>for</strong>mation can be left out<br />

where this in<strong>for</strong>mation is not required to identify the transition, <strong>for</strong> example if<br />

there is only one incoming transition. In our example, state C has two incoming<br />

actions, both invoked on transitions from the same source state B. There<strong>for</strong>e<br />

the specification of the source state is not strictly required in the declaration and<br />

line 18 could be replaced with “onEntry: f / inC1();” without changing<br />

the program’s semantic. Missing source states, trigger events, and guards, in<br />

entry-action declarations are considered to match all possibilities. There<strong>for</strong>e, in<br />

the declaration “onEntry: / a();” in the definition of a state S, action a()<br />

would be executed each time S was entered. When multiple declarations match,<br />

all matching entry actions are executed.<br />

Entry Actions on Initial <strong>State</strong> Entry<br />

In order to trigger computational actions when entering the initial state of a<br />

state machine (either at the start of the entire program or because a superstate


6.1. OSM Specification Language 103<br />

has been entered), OSM uses incoming transitions as specified by the onEntry<br />

keyword. To distinguish initial state entry from the reentry of the same state<br />

(e.g., through a self transition), the pseudo event start is introduced. The start<br />

pseudo event is not a regular event. Particularly, it is never inserted into the<br />

event queue. For hierarchical state entry, it can be considered a placeholder <strong>for</strong><br />

the actual event that triggered the entry of the superstate, though non of the<br />

event’s parameters can be accessed. An entry action triggered by start should<br />

never have a source state specified. In the example program PROG_1, the start<br />

pseudo event is used to distinguish between the entry of state A as an initial<br />

state and its reentry through the self transition specified in line 7.<br />

Variable Definitions<br />

<strong>State</strong> variables can only be defined in the scope of a state. They are visible only<br />

in that state and its substates, if any. Variables are typed and have a name, by<br />

which they can be referenced in actions anywhere in their scope. In the code of<br />

the previous example, each state defines an integer variable a, b, and c (in lines 4,<br />

11, and 17), respectively (though they are never used).<br />

Actions Parameters<br />

Actions may have numerical constants and any visible state variables as parameters.<br />

Action parameters must be declared explicitly in the declaration of an<br />

action. For example, in the code fragment below, outA() has two parameters:<br />

the state variables index and buffer.<br />

e / outA( index, buffer ) -> T ;<br />

Actions map to functions of the same name in the host language. For each host<br />

language, there is a language mapping, which defines how actions and their parameters<br />

map to function signatures. Programmers must then implement those<br />

functions. An example of the mapping of an action with parameters into the C<br />

language is shown in Sect. 6.2.1 below.<br />

6.1.2 Grouping and Hierarchy<br />

An entire state machine (composed of a single state or several states) can be<br />

embedded into a single state (which then becomes a superstate). In OSM, states<br />

can be embedded into a state simply by adding their definitions to the body<br />

of that state. <strong>State</strong>s can be nested to any level, that is, substates can embed state<br />

machines themselves. In Prog. 6.2 a state machine composed of two states, A and<br />

D, is embedded into the top-level state PROG_2, while B and C are embedded<br />

into PROG_2’s substate A (lines 5-15).<br />

The states of the embedded state machine must only contain transitions to<br />

states on the same hierarchy level. At least one of the states must be marked as<br />

the initial state. Each state of a flat state machine must be reachable through<br />

a transition from the machine’s initial state.<br />

All states of the embedded state machine may refer to variables that are defined<br />

in any of their superstates. These variables are said to be external of that


104 Chapter 6. Implementation<br />

Program 6.2: A hierarchical state machine and its representation in the OSM textual<br />

language.<br />

PROG_2<br />

A<br />

int a<br />

outB()<br />

B<br />

int b<br />

prB()<br />

f<br />

C<br />

int c<br />

inB(a,b)<br />

f<br />

outC(a,c)<br />

e<br />

e<br />

D<br />

1 state PROG_2 {<br />

2 initial state A {<br />

3 var int a;<br />

4<br />

5 initial state B {<br />

6 var int b;<br />

7 onEntry: f / inB(a, b);<br />

8 f / outB() -> C;<br />

9 onPreemption: prB();<br />

10 } // end state B<br />

11 state C {<br />

12 extern var int a;<br />

13 var int c;<br />

14 f / outC(a, c) -> B;<br />

15 } // end state C<br />

16<br />

17 e / -> D;<br />

18 } // end state A<br />

19<br />

20 state D { e / -> A; }<br />

21 }<br />

state. If desired, the external variable interface may be declared explicitly, but if<br />

it is declared, external variables must be marked with the extern keyword. In<br />

the example, the integer variable a, defined in state A (line 3), is visible in A’s<br />

substates B and C. There<strong>for</strong>e a can be used in their actions, such as inB() and<br />

outC() in lines 7 and 14 of the example Prog. 6.2, respectively. In state C the<br />

external variable a is declared explicitly.<br />

Preemption Actions<br />

Substates may declare preemption actions that are executed at runtime when<br />

the substate is left because its superstate is left through a transition. Preemption<br />

actions are declared in the context of states—they are not explicitly associated<br />

with particular transitions. Definitions of preemption actions have the following<br />

<strong>for</strong>m (where apreemption() is the preemption action):<br />

onPreemption: / apreemption();<br />

In the example Prog. 6.2, state B declares a preemption action prB() in line 9. It<br />

could be used to release resources previously allocated in the entry action inB()<br />

when B is left implicitly by preemption, that is, when leaving B’s superstate A<br />

through a transition to D.


6.1. OSM Specification Language 105<br />

6.1.3 Parallel Composition<br />

Parallel compositions of states can be defined by concatenating two or more<br />

states with “||”. In the OSM textual language, parallel state compositions must<br />

be contained in a superstate. On the next lower level, a superstate can either<br />

contain a parallel composition of states or a sequential state machine, but never<br />

both. That means, if a superstate is to contain two parallel state machines of<br />

two or more states each, an additional hierarchy level is required. This case is<br />

shown in Prog. 6.3, which is the textual OSM representation of the state machine<br />

depicted in Fig. 5.8 (b) (the original figure is reprinted here <strong>for</strong> convenience).<br />

In the example, state B contains two state machines of states D, E and F , G,<br />

respectively. In the given OSM program, each of those machines is embedded<br />

into an anonymous state (lines 5-8 and lines 10-13; not shown in the figure),<br />

which is a direct substate of B. <strong>State</strong>s that need not be referenced by transitions<br />

may be anonymous, that is, they need not have a name. Such states are initial<br />

states with no incoming transitions and states grouping parallel machines (as in<br />

the example).<br />

Program 6.3: Representation of the parallel state machine depicted in Fig. 5.8 (b)<br />

(reprinted here <strong>for</strong> convenience) in the OSM textual language.<br />

PROG_3<br />

A<br />

C<br />

e<br />

f<br />

B<br />

g<br />

D F<br />

g<br />

h h<br />

E G<br />

1 state PROG_3 {<br />

2 initial state A { e / -> B; }<br />

3<br />

4 state B {<br />

5 state {<br />

6 initial state D { g /-> E; }<br />

7 state E { g /-> D; }<br />

8 }<br />

9 ||<br />

10 state {<br />

11 initial state F { h /-> G; }<br />

12 state G { h /-> F; }<br />

13 }<br />

14 f / -> C;<br />

15 } // end state B<br />

16 state C {} // end state C<br />

17 } // end state PROG<br />

6.1.4 Modularity and Code Reuse through Machine<br />

Incarnation<br />

To foster modularity and the reuse of program code, OSM supports the instantiation<br />

of states. A programmer may instantiate a state that is defined elsewhere<br />

in the program (or in another file implemented by another developer) in order to<br />

substitute a state definition anywhere in the program. There are no restrictions<br />

on the state to be incarnated except that recursive instantiation is not allowed. A


106 Chapter 6. Implementation<br />

state definition may be substituted using the incarnate keyword followed by<br />

the name of the actual state declaration. In order to adapt the actual state definition<br />

to the program context of its incarnation, a number of name-substitutions<br />

and additions can be made to the original declaration in the incarnation statement.<br />

The full declaration of a state incarnation has the following <strong>for</strong>m (where<br />

S is the name of the state incarnation, and I is the state to be incarnated):<br />

state S incarnate I (name substitutions) {<br />

additional entry actions<br />

additional transitions<br />

additional preemption actions<br />

}<br />

Additionally, state definitions may be marked with the template keyword<br />

in order to denote that the definition is not a complete program but instead is a<br />

template definition meant <strong>for</strong> incarnation elsewhere in the program. Though not<br />

strictly required, state templates should specify their complete interface including<br />

variables, events, and actions. The example Prog. 6.4 shows the definition<br />

of a template state I (lines 1-9) and its incarnation as a direct substate of the<br />

top-level state P ROG_4 (lines 14-19). Additionally, P ROG_2 (which we have<br />

defined previously in Prog 6.2) is also inserted as a direct substate of P ROG_4<br />

(lines 21-26). We will use this example to explain the various elements of the<br />

above incarnation statement in the following subsections.<br />

Name Substitution<br />

A state can be incarnated as a substate of a regularly defined (i.e., nonincarnated)<br />

state. The incarnation has the same name as the incarnated state<br />

unless given a new name in the incarnation statement. If the a state definition is<br />

incarnated more than once, the incarnation should be given a new name in order<br />

to distinguish it from other incarnations. Lines 14 and 21 of Prog. 6.4 shows<br />

how to rename incarnations.<br />

Just like regular states, incarnated states may access the variables of their superstates.<br />

The external variables of the incarnation (i.e., the ones used but not<br />

defined in the incarnation, see lines 2 and 3) are bound to the variables of the<br />

embedding superstates at compile time. External variables must have the same<br />

name and type as their actual definitions. However, since the incarnated state<br />

and the superstate may be specified by different developers, a naming convention<br />

<strong>for</strong> events and variables would be needed. In order to relieve programmers<br />

from such conventions, OSM supports name substitutions <strong>for</strong> events and external<br />

variables in the incarnation of states. This allows to integrate states that were<br />

developed independently. Line 15 of Prog. 6.4 shows how the external variable x<br />

of the original definition (line 2) is renamed to u in the incarnation. Renaming of<br />

events is done analogously. Lines 16, 22, and 23 show how events are renamed.<br />

Note that in the incarnation of PROG_2 the event names e and f are exchanged<br />

with respect to the original definition.


6.2. OSM Language Mapping 107<br />

Adding Entry Actions and Transitions<br />

When incarnating a state, transitions, entry actions, exit actions, and preemption<br />

actions can be added to the original state definition. Adding transitions and actions<br />

to the original state definition is typically used when the program context<br />

of the incarnation is not known at implementation time. In Prog. 6.4, A is the<br />

incarnation of template I. In I there are no outgoing transitions defined. In I’s<br />

incarnation A, however, a transition is added leading from A to B (line 18). Also,<br />

an incoming action inA(u) is added to the transition from B back to A (line 17).<br />

Adding Preemption Actions<br />

When incarnating a state, preemption actions can be added to the original state<br />

definition. Preemption actions can be added to react to the termination of the<br />

incarnation’s superstate. Adding preemption actions is useful, <strong>for</strong> example, to<br />

include an existing stand-alone program as a sub-phase of a new, more complex<br />

program. In our example Prog. 6.4 there are no preemption actions added to the<br />

incarnations of either states, since their common superstate P ROG_4 is never<br />

left.<br />

6.2 OSM Language Mapping<br />

An OSM language mapping defines how OSM specifications are translated into<br />

a host language. The host language is also used <strong>for</strong> specifying actions. Such a<br />

mapping is implemented by an OSM compiler. For every host language, there<br />

must be at least one language mapping. Yet, there may be multiple mappings<br />

focusing on different aspects, such as optimizations of code size, RAM requirements,<br />

execution speed, and the like. We have developed a prototypical version<br />

of an OSM compiler that uses C as a host language. Hence, we discuss how<br />

OSM can be mapped to C in the following sections. The main goal of our compiler<br />

prototype is to create executable OSM programs that<br />

• have low and predictable memory requirements and<br />

• are executable on memory-efficient sensor-node operating systems.<br />

In general, OSM programmers do not need to know the details of how OSM<br />

programs are mapped to executable code. What they do need to know, however,<br />

is how actions in OSM are mapped to functions of the host language. Programmers<br />

need to provide implementations of all actions declared in OSM, and thus<br />

need to know their exact signature.<br />

Internally, and typically transparently to the developer, our compiler prototype<br />

maps the control structure and state variables of OSM programs separately.<br />

<strong>State</strong> variables are mapped to a static in-memory representation using<br />

C-language structs and unions. This mapping requires no dynamic memory<br />

management and is independent of the mapping of the control structure.<br />

Yet it is memory efficient, as state variables with non-overlapping lifetimes are<br />

mapped to overlapping memory regions. Also, the size of the in-memory representation<br />

of all state variables is determined at compile time.


108 Chapter 6. Implementation<br />

Program 6.4: Example <strong>for</strong> the incarnation of states. PROG_4 incarnats two<br />

states. The fully specified state template I (defined in lines 1-9)<br />

is incarnated in line 14 as state A. And the self-contained OSM<br />

program with top-level state PROG_2 from Fig. 6.2 is incarnated in<br />

line 21 as state B.<br />

PROG_4<br />

char u<br />

template I<br />

extern char x<br />

I1<br />

g<br />

inA(u)<br />

I2<br />

outI2( x )<br />

template I as A<br />

extern char u<br />

I1<br />

e<br />

I2<br />

g<br />

outI2( u )<br />

state PROG_2 as B<br />

...<br />

f<br />

inB(u)<br />

1 template state I {<br />

2 extern var char x;<br />

3 extern event g;<br />

4 action outI1( char );<br />

5<br />

6 initial state I1 {<br />

7 g / outI1( x ) -> I2; }<br />

8 state I2 {}<br />

9 }<br />

10 state PROG_4 {<br />

11 var char u;<br />

12 extern events e,f,g;<br />

13<br />

14 initial state A incarnate I (<br />

15 char u as x,<br />

16 event e as g ) {<br />

17 onEntry: B -> g / inA( u );<br />

18 f / -> B;<br />

19 } // end state A<br />

20<br />

21 state B incarnate PROG_2 (<br />

22 event e as f,<br />

23 event f as e ) {<br />

24 onEntry: A -> f / inB( u );<br />

25 g / -> A;<br />

26 } // end state B<br />

27 }<br />

The program’s control structure is mapped to executable code that manages the<br />

internal program state (as opposed to state modeled as state variables) and triggers<br />

the execution of actions. Because actions access state variables, the controlstructure<br />

mapping depends on the mapping of state variables. The generated<br />

code closely resembles conventional event-driven program code. It is executable<br />

on (slightly modified, yet) memory-efficient system software <strong>for</strong> event-driven<br />

programs.


6.2. OSM Language Mapping 109<br />

6.2.1 Variable Mapping<br />

A language mapping must define how state variables are allocated in memory,<br />

yielding an in-memory representation R of all state variables of an OSM specification.<br />

Our main goal is to minimize the memory footprint. The allocation can<br />

be defined recursively as follows, where every state of an OSM program has a<br />

representation in the C memory mapping.<br />

A single variable is mapped to a memory region Rvar that is just big enough to<br />

hold the variable. A state results in a representation Rstate, which is defined as a<br />

sequential record (i.e., a C struct) of the representations Rvari of all variables i<br />

and the representation of embedded states, if there are any.<br />

The representation of embedded states differs <strong>for</strong> sequential and parallel compositions.<br />

Since a sequential machine can be in only one state at a time, the<br />

state variables of different states of the sequential composition can be mapped<br />

to overlapping memory regions. Hence, the representation Rseq <strong>for</strong> a sequential<br />

state machine is defined as the union (i.e., a C union) of the representations<br />

Rstatei of all states i. The states of parallel compositions, however, are always active<br />

at the same time, and so are their state variables. Hence, the representation<br />

Rpar of a set of parallel states is defined as a sequential record of the representations<br />

Rstatei of all contained states i.<br />

In the example shown in Program 6.5, the state machine to the left results in<br />

the memory layout in the C language shown to the right. If an int consists<br />

of 2 bytes, then the structure requires 8 bytes. If all variables were global, 12<br />

bytes would be needed. Note that the size of the required memory as well as<br />

the memory locations of variables are already known at compile time. With<br />

this mapping, no memory management is required at all. Particularly, no dynamic<br />

allocations are per<strong>for</strong>med at runtime. Instead, the resulting in-memory<br />

representation is statically allocated once (in the code generated from the OSM<br />

control structure) and does not change during runtime.<br />

Now we can already define our mapping from OSM actions to function signatures<br />

in C. OSM action names translate to C functions of the same name. In<br />

the C mapping, state variables can be accessed by their name after dereferencing<br />

the variable’s associated state in the memory mapping. For example, the state<br />

variable c of state C (line 4) maps to _statePROG_5._par_C_D._stateC.c.<br />

In order to allow their modification in actions, state variables are passed by reference<br />

in the invocation. For example, the action outA(c) used in line 8 of the<br />

OSM program has the following signature in C:<br />

outA( *int );<br />

Its invocation from the executable code that is generated from OSM control<br />

structures is mapped to the following C function call (which is not visible to<br />

the programmer):<br />

outA( &_statePROG_5._par_C_D._stateC.c );<br />

6.2.2 Control Structures<br />

Fig. 6.1 suggests that OSM specifications are translated directly into program<br />

code of the host language. In our current prototype of the OSM compiler, this


110 Chapter 6. Implementation<br />

Program 6.5: Example of an OSM program containing several state variables<br />

side-by-side with the its variable mapping in the host language C.<br />

Every state mapps into a corresponding union, containing both<br />

the variables of that state plus a representation of the state’s substates.<br />

Sequential and parallel compositions of substates are represented<br />

as a union and a struct of substate representations, respectively.<br />

1 state PROG_5 {<br />

2<br />

3 state C {<br />

4 var int c;<br />

5<br />

6 state A {<br />

7 var int a1, a2;<br />

8 e / outA(c) -> B;<br />

9 } // end state A<br />

10 state B {<br />

11 var int b1, b2;<br />

12 f / -> A;<br />

13 } // end state B<br />

14<br />

15 } // end state C<br />

16 ||<br />

17 state D {<br />

18 var int d;<br />

19 }<br />

20<br />

21 } // end state PROG_5<br />

1 struct statePROG_5 {<br />

2 struct par_C_D {<br />

3 struct stateC {<br />

4 int c;<br />

5 union seq_A_B {<br />

6 struct stateA {<br />

7 int a1, a2;<br />

8<br />

9 } _stateA;<br />

10 struct stateB {<br />

11 int b1, b2;<br />

12<br />

13 } _stateB;<br />

14 } _seq_A_B;<br />

15 } _stateC;<br />

16<br />

17 struct stateD {<br />

18 int d;<br />

19 } _stateD;<br />

20 } _par_C_D;<br />

21 } _statePROG_5;<br />

is only true <strong>for</strong> the variable mapping. Control structures are first mapped to an<br />

intermediate representation in the imperative language Esterel [19, 18, 26]. From<br />

this representation, C code is generated by an Esterel compiler.<br />

The Esterel Synchronous Language<br />

Esterel is a synchronous language (see, <strong>for</strong> example, [17, 50, 95]) <strong>for</strong> the specification<br />

of reactive systems. Synchronous languages are reactive in that they react<br />

to input stimuli (i.e., events) in order to compute output events, typically also<br />

changing the internal program state. Synchronous languages are typically used<br />

to implement control systems, such as, industry process control, airplane and<br />

automobile control, embedded systems, bus interfaces, etc. They are built on<br />

the hypothesis that operations take no (real) time, that is, operations are atomic<br />

and the output of an operation is synchronous with its input (hence the name<br />

synchronous language). Synchronous languages have a discrete model of time<br />

where time progresses only on the occurrence of events.<br />

The Esterel language allows to specify concurrent processes, where individ-


6.2. OSM Language Mapping 111<br />

ual processes advance on the occurrence of specified events only. In each step,<br />

the system considers one or more input events. According to the input, possibly<br />

multiple Esterel processes are invoked simultaneously. When invoked, Esterel<br />

processes can emit (possibly multiple) external events. They can also invoke<br />

procedures. External events and procedures are implemented in Esterel’s<br />

host-language C. External events are typically used to drive external controller<br />

systems while procedures are used to implement complex calculations in the<br />

host language C. Programs specified in the Esterel language can be compiled<br />

to C and hardware circuits. For the C-code generation there are three different<br />

modes, optimized <strong>for</strong> execution speed, compilation speed, and code size. Pure<br />

Esterel programs, that is, programs that do not call any procedures, can also be<br />

compiled to hardware-circuit netlists in the Berkeley Logic Interchange Format.<br />

Because OSM relies heavily on Esterel’s external procedures <strong>for</strong> the implementation<br />

of actions, hardware-circuit generation is not an option.<br />

Building Programs with Esterel<br />

Esterel programs do not compile to self-contained programs. Rather, they compile<br />

to a number of C functions: one principal function and one input function<br />

<strong>for</strong> each possible input event. Additionally, a function stub is generated <strong>for</strong> each<br />

output event and each procedure, which then have to be implement by programmers<br />

to complete the program.<br />

The principal C function per<strong>for</strong>ms a discrete time step of the Esterel program<br />

based on the input events. Input events are passed to the program by calling<br />

the corresponding event-input functions be<strong>for</strong>e the principal function per<strong>for</strong>ms<br />

the next discrete step. In the principal function the program’s internal state is<br />

updated and all procedures are invoked (as well as output events “emitted”)<br />

by calling the corresponding function stubs. (It should be noted that <strong>for</strong> programmatically<br />

emitting events, OSM does not use Esterel’s synchronous event<br />

output. Instead, output events are feed into OSM’s event queue.) In order to<br />

build a full program, programmers also have to supply a main program body<br />

that collects event inputs, injects them into the system (via input functions), and<br />

then calls Esterel’s principal function. This main body resembles the control<br />

loop of event-driven systems.<br />

When implementing programs on real systems, the synchronous time model<br />

of synchronous languages conflicts with real execution semantics, where operations<br />

always require some amount of real time to execute. There<strong>for</strong>e, to implement<br />

actual programs, programmers must guarantee that input events are<br />

only injected into the system after the processing of the previous step has completed.<br />

This is typically achieved by buffering input events during the execution<br />

of Esterel’s principal function, that is, while per<strong>for</strong>ming a synchronous step in<br />

Esterel. Another approach is to guarantee that at runtime the time interval between<br />

any two consecutive input events is longer than the execution time of the<br />

principal function. Since the arrival time of external events is typically beyond<br />

the control of the programmer, however, this is infeasible <strong>for</strong> many systems.<br />

The system resulting from the <strong>for</strong>mer approach (i.e., queuing) can be seen<br />

as an event-driven system, where Esterel’s principal function takes the role of<br />

a unified event handler <strong>for</strong> all events. Such a system relies on an underlying


112 Chapter 6. Implementation<br />

runtime environment to provide an event queue that can hold event sets, and<br />

drivers that generate events. The difference between such a system and a traditional<br />

event-driven system is that that latter only processes one event at a time<br />

while in the <strong>for</strong>mer several events can be processed concurrently.<br />

Reasons <strong>for</strong> Building OSM with Esterel<br />

We have chosen to implement the OSM-to-C mapping with Esterel as intermediate<br />

language <strong>for</strong> several reasons. As a synchronous and reactive language,<br />

Esterel is well suited <strong>for</strong> the implementation of state machines. Esterel’s programming<br />

model and code structure is much closer to OSM as sequential programming<br />

languages, such as C. Indeed, Esterel has been used to implement a<br />

graphical state-based implementation language called SyncCharts [10, 11]. Our<br />

mapping from state-based OSM to imperative Esterel is inspired by this implementation.<br />

Also Esterel produces lightweight and system-independent C-code<br />

that scales well with the number of states and events and requires no support<br />

<strong>for</strong> multi-tasking. Instead, code generated from Esterel can be integrated well<br />

with a modified version of our BTnode system software, which only required<br />

moderate ef<strong>for</strong>t to support concurrent event sets.<br />

On the practical side, Esterel is immediately available and well documented.<br />

Also, the Esterel compiler per<strong>for</strong>ms sophisticated analysis of the input sources,<br />

thereby detecting logical errors in the intermediate Esterel representation that result<br />

from erroneous OSM specifications. This approach relieved us from implementing<br />

strict error-detection mechanisms in the prototype compiler ourselves<br />

and allowed to focus on the development of the OSM programming model.<br />

6.2.3 Mapping OSM Control-Flow to Esterel<br />

In our mapping of OSM, Esterel’s principal function per<strong>for</strong>ms a discrete time<br />

step of the OSM state machine. In each OSM machine step the state transition<br />

function accepts the set of concurrent events from OSM’s event queue (see Section<br />

5.4.1). In that machine step, the state transition function first invokes all<br />

preemption and exit actions of the current state and its substates. Then it per<strong>for</strong>ms<br />

the state transitions triggered by the set of input events. Finally it invokes<br />

all entry actions of the newly assumed states.<br />

Effectively, state-based OSM programs are compiled back into an eventdriven<br />

program representation where all event types invoke a single action implemented<br />

by Esterel’s principal function. As a matter of fact, the C code generated<br />

from the Esterel representation of OSM programs runs on BTnodes with a<br />

slightly modified version of the event-driven BTnode system software. Demonstrating<br />

the duality between OSM programs and event-driven programs is the<br />

prime goal of the language mapping and an important piece in supporting our<br />

thesis. This duality is the basis <strong>for</strong> showing that state-based programs, such as<br />

OSM, do not require more system support than regular event-driven programs.<br />

We still need to show, however, that the state-transition function generated from<br />

Esterel (i.e., the function that implements an OSM program as event-handler)<br />

does not introduce unacceptable overhead. We will do so below.


6.2. OSM Language Mapping 113<br />

Esterel-Code Generation from OSM<br />

The exact details of mapping OSM to Esterel are only of secondary importance.<br />

There<strong>for</strong>e, and due to space constraints, we will present the mapping by example<br />

only. Appendix A.1 shows the OSM program PROG_PAR depicted in<br />

Fig. 6.2, its mapping to Esterel, and the C code generated by the Esterel compiler.<br />

The program is a parallel composition of the OSM state machines presented<br />

in Programs 6.1 and 6.2. It has 10 states and contains most features of<br />

OSM. Table 6.1 compares the sizes (in terms of lines of code) of the original OSM<br />

implementation and the generated C and Esterel mapping code.<br />

Compilation<br />

Unit<br />

# Lines Comment<br />

test.osm 49 original OSM implementation<br />

test.h 80 variable mapping (C)<br />

test.strl 307 control structure mapping (Esterel)<br />

test.c 616 control structure mapping (C)<br />

Table 6.1: Number of lines of code <strong>for</strong> an OSM program and its mappings in C<br />

and Esterel.<br />

In this section we demonstrate that the event-driven code generated by OSM<br />

does not introduce unacceptable overhead. To this end, we analyze the variable<br />

memory and code size of OSM programs. As described previously, the OSM<br />

code is first translated into Esterel code, from which C code is generated. There<strong>for</strong>e<br />

we analyze the object code generated from C code by a C compiler. Since<br />

our target plat<strong>for</strong>m is the BTnode, we compile <strong>for</strong> the Atmel AVR 128 microcontroller,<br />

the BTnode’s CPU. The compiler used is GCC <strong>for</strong> AVR (avr-gcc), version<br />

3.4.3.<br />

We start by analyzing the Programs 6.1 and 6.2 from the last section. Using<br />

these programs as base modules, we the build more complex programs by sequential<br />

and parallel composition.<br />

C-Code Generation from Esterel<br />

The Esterel compiler has several code generation modes and target languages.<br />

We use ANSI C in the sorted equations mode. The original C output of the<br />

Esterel compiler <strong>for</strong> program P ROG_P AR depicted in Fig. 6.2 is shown in Appendix<br />

A.4.1. The state transition function basically compiles into a long list of<br />

boolean equations that operate on a large amount of boolean variables. These<br />

variables store the machine’s internal program state as well as temporary state<br />

during the function’s execution in two large array of chars. The representation<br />

of boolean values as chars is inherently memory inefficient. There<strong>for</strong>e we modify<br />

the Esterel-generated C code to use bitfields instead, as exemplified below.<br />

Bitfields are a C mechanism <strong>for</strong> addressing individual bits of a struct. The necessary<br />

modifications are made by a Perl script but currently require some human<br />

intervention, though the task could be fully automated. Note that these modifications<br />

are required only when compiling <strong>for</strong> the BTnode. Development and<br />

initial program testing is typically per<strong>for</strong>med in the BTnode emulation environment<br />

on a PC, where size is not an issue.


114 Chapter 6. Implementation<br />

PROG_PAR<br />

PROG_1<br />

inA()<br />

A<br />

outA()<br />

e<br />

int a<br />

B<br />

int b<br />

C<br />

int c<br />

f<br />

outA()<br />

outB()<br />

f e<br />

inC1() inC2()<br />

PROG_SEQ<br />

PROG_1<br />

inA()<br />

A<br />

outA()<br />

e<br />

int a<br />

B<br />

int b<br />

C<br />

int c<br />

f<br />

outA()<br />

outB()<br />

f e<br />

inC1() inC2()<br />

g<br />

g<br />

PROG_2<br />

D<br />

int a<br />

E<br />

outE()<br />

int b<br />

prE()<br />

f<br />

F<br />

PROG_2<br />

D<br />

int a<br />

int a<br />

int c<br />

E<br />

outE()<br />

inE(a,b)<br />

f<br />

int b<br />

prE()<br />

F<br />

int a<br />

int c<br />

outF(a,c)<br />

inE(a,b)<br />

outF(a,c)<br />

outD()<br />

e<br />

G<br />

e<br />

outD()<br />

Figure 6.2: Two OSM programs PROG_PAR and PROG_SEQ composed of the<br />

state machines PROG_1 and PROG_2 previously presented as Programs<br />

6.1 and 6.2, respectively . In PROG_PAR, the two state machines<br />

are composed in parallel, while in PROG_SEQ they are composed<br />

sequentially, introducing a new event g to transition between<br />

them.<br />

f<br />

f<br />

e<br />

G<br />

e


6.3. OSM Compiler 115<br />

// original representation // optimized representation<br />

char E4; struct {<br />

unsigned int E0:1;<br />

unsigned int E1:1;<br />

unsigned int E2:1;<br />

unsigned int E3:1;<br />

} E;<br />

The obvious advantage of using bitfiels instead of chars <strong>for</strong> the storage of<br />

boolean values is a reduction of data-memory requirements by a factor of eight.<br />

The disadvantage is an increase of program memory by a factor of about 1.8.<br />

The size increase stems from the necessity to mask individual bits in the struct.<br />

In the disassembly of the modified state-transition function, bit masking shows<br />

as additional machine operations in the object code, mainly immediate AND operations<br />

(i.e., AND with a constant) and register loads. The program-memory<br />

size of variable-memory optimized code is shown in Table 6.2.<br />

Program Code Size in bytes Memory Size in bytes<br />

Transition Function Program <strong>State</strong> <strong>State</strong> Variables<br />

PROG_PAR 9240 17 8<br />

PROG_SEQ 12472 20 6<br />

PROG_1 3170 8 2<br />

PROG_2 5434 10 6<br />

D n/a 6 6<br />

Table 6.2: Memory sizes <strong>for</strong> the states from Fig. 6.2. The programs P ROG_P AR<br />

and P ROG_SEQ are concurrently and sequentially composed of program<br />

parts P ROG_1 and P ROG_2, respectively. D is a part of program<br />

P ROG_2. The first row contains the size of the object code generated<br />

<strong>for</strong> the listed programs. The second row contains the memory<br />

requires to internally store their optimized state machine. The third<br />

row denotes the size of the state variables in memory.<br />

6.3 OSM Compiler<br />

Our OSM-compiler prototype is a three-pass compiler implemented in the Java<br />

programming language. In the first pass, the compiler parses the OSM input<br />

file and generates an intermediate representation of the program’s syntax tree in<br />

memory. The parser <strong>for</strong> the OSM-language is generated by the parser generator<br />

CUP from a description of the OSM grammar. (CUP is an acronym <strong>for</strong> Constructor<br />

of Useful Parsers.) CUP serves the same role <strong>for</strong> Java as the widely known<br />

program YACC does <strong>for</strong> C. CUP takes a grammar with embedded Java code as<br />

input (in our case the grammar of OSM), and produces a set of Java classes that<br />

implement the actual parser. The parser generated <strong>for</strong> the OSM language generates<br />

an in-memory representation of the OSM syntax tree, that is, it implements<br />

the compiler’s first pass on an OSM input file. The parser generated by CUP<br />

relies on a scanner to tokenize the input of the parser. For our OSM compiler,


116 Chapter 6. Implementation<br />

the scanner is generated by a lexical analyzer generator called JFLEX (Fast Lexical<br />

Analyzer Generator <strong>for</strong> Java). JFLEX is the Java counterpart of FLEX, a tool<br />

generating scanners in the C programming language.<br />

The second pass of the OSM compiler operates on the in-memory syntax tree<br />

(created during the first pass) of the OSM input. Concretely, it extends the inmemory<br />

representation of interfaces that are only implicitly defined in the input<br />

file (such as a complete list of variables visible <strong>for</strong> each state). It also per<strong>for</strong>ms<br />

some input verification beyond mere syntactical checks, <strong>for</strong> example, if all transition<br />

targets exist and if all variables used in actions and guards have been<br />

defined. However, error checking is mostly delegated to the Esterel compiler on<br />

the intermediate Esterel representation of the OSM program. This approach is<br />

convenient as we did not need to implement the functionality ourselves, yet can<br />

provide some error notifications. On the other hand it is very inconvenient <strong>for</strong><br />

OSM programmers as they must be able to interpret the somewhat cryptic error<br />

messages of the Esterel compiler, which in turn requires to be familiar with the<br />

Esterel language.<br />

In the third pass, the Esterel representation of the OSM input is generated<br />

according to the language mapping, as described in the previous section. This<br />

representation is then compiled to plain C code with the Esterel compiler. The<br />

final executable is produced from a C compiler by a generic makefile (see<br />

Appendix A.5.2). It compiles and links the three elements that make up an<br />

OSM program in the host-language C: (1) the C-language output of the Esterel<br />

compiler, representing the program’s control flow and structure, (2) the statevariable<br />

mappings, which are directly produced as a C header file by the OSM<br />

compiler, and (3) the definitions of actions written in C by the programmer.<br />

In the current version the OSM-compiler prototype, some features desirable<br />

<strong>for</strong> routine program development have not been implemented, mostly due to<br />

time constraints. For example, the OSM compiler does not support compilation<br />

units. Only complete OSM specifications can be processed by the compiler. Note<br />

however, that as far as compilation units are concerned, the implementation of<br />

actions is independent of the program. Actions are specified in (one or several)<br />

C files, which may be compiled separately from the OSM program (see Fig. 6.1).<br />

Also, some OSM features not supported in the current version of the compiler.<br />

These features are:<br />

• Incoming transitions with the declaration of a source state (as in<br />

onEntry: A -> e / inA();): Incoming transitions without the declaration<br />

of a source state (such as onEntry: e / inA();) work fine.<br />

This feature is currently missing, as its implementation in Esterel is rather<br />

complex.<br />

• <strong>State</strong> incarnations and thus adding and renaming actions, transitions and<br />

preemption actions to incarnations: We have shown the feasibility and<br />

usefulness of state incarnations in a previous implementation of the OSM<br />

compiler (presented in [74]). In the re-implementation, this feature was<br />

dropped due to time constraints.<br />

• Preemption actions: The realization of preemption actions in the Esterel<br />

language is straight<strong>for</strong>ward, yet, would require additional analysis in the


6.4. OSM System Software 117<br />

of the second compiler stage. We have dropped this feature due to time<br />

constraints.<br />

The entire OSM compiler currently consists of a total of 7750 lines of code,<br />

including handwritten and generated Java code (4087 and 2572 lines, respectively),<br />

as well as the specifications files <strong>for</strong> the OSM parser and scanner (707 and<br />

184 lines, respectively). The compiler runs on any Java plat<strong>for</strong>m, yet, requires an<br />

Esterel compiler [113] <strong>for</strong> the generation of C code. The Esterel compiler is freely<br />

available <strong>for</strong> the Linux and Solaris operating systems, as well as <strong>for</strong> Windows<br />

with the Cygwin environment installed.<br />

6.4 OSM System Software<br />

The OSM system software provides a runtime environment <strong>for</strong> OSM programs.<br />

Because OSM programs are compiled into a basically event-driven program notation,<br />

the runtime environment <strong>for</strong> OSM programs is very similar to regular<br />

event-driven programs. In fact, OSM programs run on a slightly modified version<br />

of the BTnode system software, our previous, purely event-driven operating<br />

system, which we have already described in Sect. 3.5.1. Only small modifications<br />

to the control loop, the event queue, and the event representation were<br />

necessary in order to support concurrent events. As we pointed out in Sect. 5.4.1,<br />

OSM considers the set of events emitted during a single step as concurrent.<br />

In the original BTnode system software, events are implemented as 8-bit values,<br />

being able to represent 255 different types of events (event number 0 is reserved).<br />

The event queue is implemented as a ringbuffer of individual events. In<br />

the revised implementation, events are implemented as 7-bit values, effectively<br />

reducing the number of event types that the system can distinguish to 127. The<br />

8th bit is used as a flag to encode the event’s assignment to a particular set of<br />

concurrent events. Consecutive events in the ringbuffer with the same flag value<br />

belong to the same set. Internally, the queue stores two 1-bit values, one <strong>for</strong> denoting<br />

the current flag value <strong>for</strong> events to be inserted, the other <strong>for</strong> denoting the<br />

current flag value <strong>for</strong> events to be removed from the queue, respectively.<br />

While the incoming flag value does not change, events inserted into the queue<br />

are considered to belong to the same set of events (i.e., their flag is set to the<br />

same value). To indicate that a set is complete and the next event to be inserted<br />

belongs a different set, the new queue operation next_input_set is introduced.<br />

Internally, next_input_set toggles the value of the flag <strong>for</strong> inserted events.<br />

Likewise, the queue’s dequeue operation returns the next individual event<br />

from the queue as indicated by the current output flag. The event number 0<br />

is returned if there are no more events <strong>for</strong> the specified set. To switch to the next<br />

set of events the queue operation next_output_set is introduced. Additionally,<br />

the operation’s return value indicates whether there are any events in the queue<br />

<strong>for</strong> the next set or the queue is empty. Both, the enqueue and dequeue operations<br />

retain their signatures.<br />

The control loop also needs to reflect the changes made to the event queue.<br />

The fragment below shows the implementation of the new control loop. Instead<br />

of dequeuing individual events and directly invoking the associated event handlers,<br />

the new version dequeues all events of the current set (line 5) and invokes


118 Chapter 6. Implementation<br />

the input functions generated by the Esterel compiler (line 6). When all events<br />

of the current set have been dequeued, the principal event handler of the OSM<br />

program is invoked (line 8). Then the next set of events to be dequeued is activated<br />

(line 10). If there are no events pending, the node enters sleep mode<br />

(line 11). The node automatically wakes up from sleep mode when a new event<br />

is available. Then the process will be repeated.<br />

1 event_t event;<br />

2<br />

3 while( true ) {<br />

4<br />

5 while( (event = dequeue()) != 0 )<br />

6 input_to_next_step( event );<br />

7<br />

8 per<strong>for</strong>m_next_step();<br />

9<br />

10 if( next_output_set() == false ) sleep();<br />

11<br />

12 } // end while<br />

Note that the next_input_set operation is not called from the main loop directly.<br />

It is instead called twice at the end of the function per<strong>for</strong>ming the next<br />

OSM machine step: Once after inserting all events into the queue that have been<br />

programmatically emitted by preemption and exit actions. And again after inserting<br />

all events that have been programmatically emitted by incoming actions.<br />

6.5 Summary<br />

In this chapter we have presented the pieces needed to implement programs<br />

based on the concepts of OSM and to compile them into executable code on an<br />

actual sensor node. We have presented OSM’s specification language that allows<br />

to specify sensor-node programs, a compiler to translate OSM program specifications<br />

into portable C code, and we have shown how to modify an existing<br />

event-driven sensor-node operating system in order to support OSM programs<br />

at runtime.


7 <strong>State</strong>-based <strong>Programming</strong> in<br />

Practice<br />

The thesis <strong>for</strong>mulated in the introduction of this dissertation was that the statebased<br />

programming model incurs as little overhead as the event-based model,<br />

yet allows to program more memory efficiently and allows to specify programs<br />

more structured and more modular compared to event-driven programs. In the<br />

following sections we will support this thesis.<br />

Be<strong>for</strong>e turning to concrete examples, we present an intuitive approach to motivate<br />

our claim in Sect. 7.1. We explain how event-driven programs can be<br />

<strong>for</strong>mulated as trivial OSM programs containing a single state only. Clearly, such<br />

programs do not exhibit the benefits of state-based programming. However, the<br />

initially trivial programs are easily refined, increasing their structure, modularity,<br />

and memory efficiency. Such refined programs, on the other hand, cannot be<br />

translated back in the event-driven model without loosing such benefits again.<br />

In Sect. 7.2 we show how the structure and modularity of sensor-node programs<br />

benefits from OSM using a concrete example from the literature. We will<br />

compare the reimplementation of a large part of the EnviroTrack middleware in<br />

OSM and compare it to the original NesC implementation. We will compare the<br />

two implementations with respect to state management, accidental concurrency,<br />

and state-bound resource initialization. We show that with OSM manual state<br />

management can be removed entirely in favor of an explicit, high-level state notation.<br />

As a result the programs are much more concise. In our example, we<br />

show that the size of the OSM re-implementation is reduced by 31% (measured<br />

in lines of code) with respect to the original nesC implementation.<br />

In Sect. 7.3 we will illustrate how OSM can increase the memory efficiency of<br />

programs compared to event-driven programs. Concretely, we will show how<br />

state variables foster the reuse of memory <strong>for</strong> temporary data structures using<br />

a small example. The memory savings achievable by using state variables very<br />

much depend on the program structure. While very high memory savings can<br />

be achieved in theory, we expect savings of 10 to 25% in real-world programs.<br />

Sect. 7.4 summarizes this chapter.<br />

7.1 An Intuitive Motivation<br />

Traditional event-based programs can be <strong>for</strong>mulated in OSM as depicted in<br />

Fig. 7.1. There is a single state S0, which has attached all global variables vari<br />

of the event-based program. For each possible event ej there is a self transition<br />

with an associated action outj(), which has access to ej and to all state variables<br />

of S0. Hence, OSM can be considered a natural extension of event-based programming.


120 Chapter 7. <strong>State</strong>-based <strong>Programming</strong> in Practice<br />

S0<br />

var1<br />

var2<br />

out1()<br />

out2()<br />

Figure 7.1: A traditional event-based program specified in OSM.<br />

One notable observation is that OSM supports two orthogonal ways to deal<br />

with program state: explicit machine states and manual state management using<br />

variables. In traditional event-based programming, all program state is expressed<br />

via global variables. In pure finite state machines, all program state is<br />

expressed as distinct states of the FSM. With OSM, programmers can select a<br />

suitable point between those two extremes by using explicit machine states only<br />

where this seems appropriate. In particular, a programmer can start with an existing<br />

event-based program, “translate” it into a trivial OSM program as shown<br />

in Fig. 7.1, and gradually refine it with more states and levels of hierarchy. Naturally,<br />

the refined program is more structured.<br />

However, the refined program structure cannot be easily translated back into<br />

the event-driven model. To express OSM programs in the event-driven model,<br />

the state structure needs to be managed with variables. Manual management<br />

has the implications described previously and is a heavy burden <strong>for</strong> programmers.<br />

In OSM, the translation from the state-based to the event-driven program<br />

notation, and thus the state management, is automated by the OSM compiler, as<br />

described in the previous chapter. Through the translation step we can ensure<br />

that the underlying system software is basically the same and thus as efficient<br />

as in event-driven systems, yet achive the structural benefits of state-based programming.<br />

Another notable implication of the mapping from the event-driven model to<br />

the state-based model is that OSM programs are typically more memory efficient<br />

than traditional event-based programs. In the trivial OSM program depicted in<br />

Fig. 7.1, as well as in event-driven programs, all variables are active all of the<br />

time. Hence, the memory consumption equals the sum of the memory footprints<br />

of all these variables. In OSM specifications with multiple states, the same set<br />

of variables is typically distributed over multiple distinct states. Since only one<br />

state of a set of distinct states can be active at a time, the memory consumption of<br />

state variables equals the maximum of the memory footprints among all distinct<br />

states.<br />

Note that in terms of memory consumption, TinyOS programs are like regular<br />

event-driven programs and OSM’s concept of state variables cannot be applied.<br />

Though TinyOS offers components to structure the code, they do so in the functional<br />

domain rather than in the time domain. That means that components are<br />

always active and thus their variables cannot be mapped to overlapping memory<br />

regions.<br />

After this intuitive explanation of the benefits of OSM over pure event-driven<br />

programming, we will now discuss the various benefits of OSM using concrete<br />

examples in the following sections.<br />

e1<br />

e2


7.2. Modular and Well-Structured Program Design 121<br />

7.2 Modular and Well-Structured Program Design<br />

In Chapter 4 we have reviewed the limitations of the event-driven programming<br />

model. We identified manual state management as an impediment to writing<br />

modular and well-structured program code and as an additional source of errors.<br />

In this section we now illustrate how event-driven programs can benefit<br />

from a state-based program notation by reimplementing a major part of the EnviroTrack<br />

middleware, namely its group management, in OSM. We show that<br />

the OSM code is not only much more readable and structured and thus easier to<br />

change, it is also significantly shorter then its original implementation in nesC.<br />

We start by giving an overview of the EnviroTrack middleware and explain<br />

the original design of its group-management protocol. Then we analyze its original<br />

implementation in NesC and present a reimplementation in OSM. Finally<br />

we compare both approaches with respect to the number of code lines needed<br />

<strong>for</strong> their specification.<br />

7.2.1 EnviroTrack Case Study<br />

We have already briefly mentioned EnviroTrack in Chap. 1. Here we will present<br />

a more detailed description. For an exhaustive description, please refer to<br />

[6, 7, 23, 54]. EnviroTrack is a framework that supports tracking of mobile targets<br />

with a sensor network. In EnviroTrack, nodes collaboratively track a mobile<br />

target (an object or phenomenon such as a car or fire) by <strong>for</strong>ming groups of spatially<br />

co-located nodes around targets. If a target is detected, the group members<br />

collaboratively establish and maintain a so-called context label, one per target.<br />

Context labels describe the location and other characteristics of targets and may<br />

have user-specified computations associated with them, which operate on the<br />

context label’s state at runtime. The state is aggregated in the group leader from<br />

all group members’ sensor values. Context labels are typed to describe different<br />

kind of mobile targets. A single node may be member of several groups, each<br />

maintaining a its own context label. When the target moves, the context-label<br />

group moves with it. Nodes no longer able to detect the target leave the group,<br />

while nodes having recently detected the target join. During target movements,<br />

EnviroTrack maintains the target’s context label, that is, its state.<br />

EnviroTrack is implemented in TinyOS / NesC on Mica2 motes. Its source<br />

code is available on the Internet [40]. EnviroTrack consists of 11 NesC components<br />

implemented in 45 source files. As discussed in Sect. 3.5.2, NesC components<br />

encapsulate state and implementation, and provide an interface <strong>for</strong> accessing<br />

them. The entire implementation consists of 2350 lines of actual source<br />

code (i.e., after having removed all empty lines, comments, and debug output).<br />

EnviroTrack Group Management<br />

The group management protocol is implemented in the NesC component<br />

GroupManagementM.nc. Its implementation accounts <strong>for</strong> 426 lines, that is, <strong>for</strong><br />

about 18% of the entire code. We have included a condensed version of Group-<br />

ManagementM.nc in Appendix B.1. Because <strong>for</strong> this discussion we are mainly<br />

interested in the program’s structure, we have removed all code not relevant <strong>for</strong>


122 Chapter 7. <strong>State</strong>-based <strong>Programming</strong> in Practice<br />

control flow in order to improve its readability. Only statements that are relevant<br />

<strong>for</strong> managing the program’s state (i.e., state-keeping, transitions as well<br />

as guards in transitions, and state initializations) have been retained. All other<br />

statements have been replaced by placeholders (opn() and [...], <strong>for</strong> operations<br />

and <strong>for</strong> boolean expressions, respectively). A few comments have been<br />

added.<br />

To maintain context labels, EnviroTrack employs a distributed group management<br />

protocol, which is described in [6]. Per context label, an EnviroTrack sensor<br />

node can be in one of four states: FREE, FOLLOWER, MEMBER, and LEADER. The<br />

state changes only on the occurrence of events: the reception of typed radio messages,<br />

timeouts, and the detection as well as the loss of the target (which is computed<br />

in a separate module and communicated via join and leave events). However,<br />

a review of the source code revealed that the actual group-management<br />

implementation is much more complex. We have reverse engineered the code to<br />

find all relevant states, events, and the state transition graph. The result is shown<br />

in Fig. 7.2. Compared to the description given in [6], the actual implementation<br />

of the protocol has three more states (NEW_CANDIDATE, LEADER_CANDIDATE,<br />

and RESIGNING_LEADER) and two more types of radio messages.<br />

FREE<br />

[else]<br />

join_ev<br />

leave_ev<br />

timeout[...]<br />

recruit_msg<br />

resign_msg<br />

[else]<br />

timeout[...]<br />

NEW_CANDIDATE LEADER_CANDIDATE<br />

timeout<br />

candidate_msg[...]<br />

recruit_msg<br />

join_ev<br />

FOLLOWER MEMBER<br />

recruit_msg<br />

resign_msg<br />

leave[...]<br />

RESIGNING_LEADER<br />

leave_ev<br />

recruit_msg<br />

candidate_msg<br />

join_ev<br />

leave_ev<br />

leave_ev[else]<br />

recruit_msg<br />

candidate_msg[...]<br />

recruit_msg[...]<br />

[else]<br />

LEADER<br />

timeout<br />

timeout<br />

resign_msg[...]<br />

recruit_msg<br />

resign_msg<br />

candidate_msg<br />

Figure 7.2: Distributed group management of EnviroTrack presented as state<br />

machine.<br />

timeout


7.2. Modular and Well-Structured Program Design 123<br />

Group-Management <strong>State</strong> Machine<br />

The main states of the group management protocol have the following meaning.<br />

A MEMBER is a network neighbor of a LEADER and is detecting the proximity of<br />

the target with its sensors. (The decision whether a target has been detected<br />

or lost is made in another component, which signals join and leave events, respectively.)<br />

Members contribute their location estimates and other sensor readings<br />

to the target’s context label, which is maintained by the group leader. A<br />

FOLLOWER is a network neighbor of a LEADER that does not detect the target<br />

itself, that is, followers do not establish a new context label. Followers become<br />

members when they start detecting the target. A FREE node does not detect the<br />

target and is not a FOLLOWER. If a free node detects a new target, it establishes<br />

a new context label and can eventually become the leader of that group. Finally,<br />

a LEADER is a MEMBER that has been elected out of all members to manage and<br />

represent a context label. All members send their location and other sensory<br />

data to the LEADER, where these locations are aggregated to derive a location<br />

estimate of the target. The LEADER frequently broadcasts recruit messages so<br />

that free nodes can detect whether they should become followers, and members<br />

know that the leader is still alive. A FOLLOWER sets up a timeout to engage in<br />

leader election when the leader has not been heard <strong>for</strong> some specified duration<br />

of time, that is, if the timeout expires. Followers not detecting the target any<br />

longer become free nodes again.<br />

The actual implementation has tree more states (NEW_CANDIDATE,<br />

LEADER_CANDIDATE, and RESIGNING_LEADER) that are used <strong>for</strong> the coordinated<br />

creation of a single leader should several nodes detect a target at<br />

(almost) the same time, to handle spurious leaders, and to per<strong>for</strong>m coordinated<br />

leader handoff. Also, there are two more messages being sent. The resign<br />

message is used to indicate that a LEADER is no longer detecting the target<br />

and is about to resign its role, so that members can start leader election in the<br />

LEADER_CANDIDATE state. Nodes that are candidates <strong>for</strong> the LEADER role<br />

(that is, nodes observing the target without previously having heard from a<br />

LEADER) send a candidate message. The candidate message is used to prevent<br />

the instantiation of several leaders if several nodes have detected the target at<br />

almost the same time. In that case several candidate message are sent. If a leader<br />

candidate receives another candidate message, the receiving node becomes a<br />

MEMBER if the sender has a higher node identifier. We will discuss the NesC<br />

implementation of this algorithm and its re-implementation in OSM in the<br />

following section.<br />

7.2.2 Manual versus Automated <strong>State</strong> Management<br />

NesC Implementation<br />

The implementation of EnviroTrack’s distributed group management protocol<br />

manages program state manually. The NesC group-management component<br />

has a dedicated private variable status <strong>for</strong> keeping the current state of the<br />

group-management state machine. (The status variable is a field in a larger<br />

structure GMStatus that holds in<strong>for</strong>mation relevant to the group management<br />

protocol, see Appendix B.1.) The seven states of the state machine are mod-


124 Chapter 7. <strong>State</strong>-based <strong>Programming</strong> in Practice<br />

eled explicitly as a NesC-language enumeration. Transitions are modeled by<br />

assigning one of the enumeration constants to the state-keeping variable. (We<br />

use the term “state-keeping variable” as opposed to “state variable” in order to<br />

distinguish them from OSM’s concept of state variables.) The code resembles<br />

the general pattern of event-driven applications as shown in Program 4.2. As<br />

such, it is very structured <strong>for</strong> an event-driven program.<br />

Program 7.1 is an excerpt of the original NesC code representing a state transition<br />

(see also Appendix B.1, lines 159-164). The MEMBER state is entered from<br />

the FOLLOWER state through a transition triggered by a join event (line 1). In<br />

order to increase the readability of the code, op7() (line 5) serves as a placeholder<br />

<strong>for</strong> the actual action to be per<strong>for</strong>med on the occurrence of the join event<br />

in state FOLLOWER. Then the transition’s target state is assigned in line 6. Finally,<br />

the timer of the target state MEMBER is initialized in line 7. We will discuss<br />

initialization of state-bound resources, such as timers, in greater detail below.<br />

Program 7.1: NesC code fragment implementing the transition from FOLLOWER<br />

to MEMBER upon a join event. The code also includes the timer<br />

initialization of the target state (line 7).<br />

1 command result_t GroupManagement.join() {<br />

2 switch( _GMStatus.status ) {<br />

3 case ...: // [...]<br />

4 case FOLLOWER:<br />

5 op7();<br />

6 _GMStatus.status = MEMBER;<br />

7 _generalTimer = wait_receive();<br />

8 break;<br />

9 case ...: // [...]<br />

10 default: // [...]<br />

11 }<br />

12 }<br />

OSM Implementation<br />

We have reimplemented EnviroTrack’s group management state machine in<br />

OSM, as shown in Appendix B.2. A small excerpt of the OSM code is shown<br />

in Prog. 7.2, which is the OSM equivalent to the NesC fragment in Prog. 7.1.<br />

Instead of using a state-keeping variable, states are specified explicitly using<br />

OSM states (indicated by the state keyword). Transitions are specified explicitly<br />

within their source state, making switch/case statements obsolete. Unlike<br />

in event-driven programs, control-flow in<strong>for</strong>mation and implementation are<br />

clearly separated in OSM programs. OSM code only contains the control flow<br />

of the group management protocol. It does not contain any program logic. The<br />

actual program is implemented in the C language in separate files.


7.2. Modular and Well-Structured Program Design 125<br />

Program 7.2: OSM code fragment implementing the transition from FOLLOWER<br />

to MEMBER upon a join event. The code also includes the timer<br />

initialization of the target state (line 6).<br />

1 state FOLLOWER {<br />

2 join_ev / op7() -> MEMBER;<br />

3 // [...]<br />

4 }<br />

5 state MEMBER {<br />

6 onEntry: / reset_timer( wait_receive() );<br />

7 // [...]<br />

8 }<br />

Comparison<br />

There are two main differences between the event-driven and the state-based<br />

programming model regarding program structure. Firstly, the order of structuring<br />

devices—states and events—is reversed. In the event-driven model, the<br />

first-level structuring device is events, or rather event handlers. In the event<br />

handlers, control flow is then manually multiplexed (with switch statements)<br />

according to the (manually coded) program state. In the state-based model of<br />

OSM, on the other hand, programs are essentially constructed from hierarchically<br />

structured states, in which the control flow is multiplexed according to<br />

events. Allowing programmers to specify their programs in terms of freely arrangeable<br />

hierarchical states offers more freedom and a better way of structuring,<br />

rather than being confined to a few predetermined event handlers. OSM<br />

program code can be divided into multiple functional units, each implemented<br />

in a state or state hierarchy. Because of the clearly defined interfaces of states,<br />

their implementation can be easily replaced by another implementation with<br />

the same interface. In general, using state machines is a more natural way to<br />

describe programs (see Sect. 4.2).<br />

The second difference between both models is that program state in OSM is a<br />

first-class abstraction and state management can thus be automated. The highlevel<br />

state notation is much more concise compared to the code in the eventdriven<br />

model, which is lacking such an abstraction and thus state has to be managed<br />

manually. From the high-level description of the program’s state structure,<br />

code <strong>for</strong> multiplexing the control flow is generated automatically and transparently<br />

<strong>for</strong> the programmer. The programmer is saved from having to manually<br />

code the control flow. The amount of code that can be saved is significant, as we<br />

will show <strong>for</strong> our EnviroTrack example.<br />

The code structure of the EnviroTrack group-management implementation in<br />

NesC (see Appendix B.1) is clearly dominated by manual stack management<br />

necessitated by the event-driven programming model. As can be seen from<br />

Tab. 7.1, out of the 426 code lines of the original NesC implementation, 187 lines<br />

(i.e., about 44%) are dedicated to manual state management. The remaining<br />

239 lines mainly contain the actual program logic (but also some declarations<br />

and interface declarations). That is, out of 9 lines of code, 4 lines are required


126 Chapter 7. <strong>State</strong>-based <strong>Programming</strong> in Practice<br />

EnviroTrack Group management Size in Lines of Code Size Reduction<br />

NesC OSM with OSM<br />

Flow control (state management) 187 (44%) 56 (19%) 70%<br />

Program logic 239 (56%) 239 (81%) n/a<br />

Total 426 (100%) 295 (100%) 31%<br />

Table 7.1: Comparison of program sizes <strong>for</strong> the implementation of EnviroTrack’s<br />

group-management protocol in NesC and OSM.<br />

to explicitly specify the program’s control flow, the remaining 5 lines implement<br />

the actual program logic.<br />

On the other hand, an equivalent program in OSM requires only 56 lines of<br />

code, that is, less than one third of the original NesC code size. In OSM, the fraction<br />

of code <strong>for</strong> managing the control flow (56 lines) against the entire program<br />

(56+239=295 lines) is down to less then 20%. That is, there is only a single line of<br />

code required to express the programs control flow to every 4 lines of program<br />

logic (which is implemented in separate files). The results of this comparison<br />

are summarized in Tab. 7.1. Note that in NesC programs the implementation<br />

of the actual program logic does not use any NesC-specific features; just like in<br />

OSM the actual program code is specified using plain C statements and is thus<br />

identical in both programming frameworks.<br />

In this section we have looked at manual state management in general. In<br />

the next section we look at special patterns of manual state management that<br />

regularly appear in typical sensor-node programs, namely state-based resource<br />

initialization and avoiding accidental concurrency.<br />

7.2.3 Resource Initialization in Context<br />

In our analysis of sensor-node programs in Sect. 4.2 we stated that the computations<br />

within a program state often utilize a well defined set of resources.<br />

Typically these resources are allocated as well as initialized when entering the<br />

program state, and released again when leaving the state. To efficiently specify<br />

this resource allocation and release, OSM offers incoming and outgoing actions<br />

(respectively). Typical examples in sensor networks are the state-based initialization<br />

and use of hardware resources, such as sensors and emitters, but also<br />

transceivers, external memories, timers, and so on. A special case of a statebound<br />

resource is memory that is only used in the context of a state, that is, state<br />

variables. We will discuss how state variables are used in practice in Sect. 7.3.<br />

As resource initialization and release are bound to state changes, these operations<br />

are closely connected to state management. In the following we will show<br />

how the state-based approach of OSM helps improving the code structure with<br />

regard to resource initialization. To do so, we will again use the EnviroTrack<br />

example.<br />

Resource Initialization in EnviroTrack<br />

In our EnviroTrack example, all states (except FREE) use a timer. The timers in<br />

the EnviroTrack group-management protocol always trigger actions specific to


7.2. Modular and Well-Structured Program Design 127<br />

the current state. These actions are either state changes or recurring computational<br />

actions within the current state. There<strong>for</strong>e, we like to think that conceptually<br />

every state uses an individual timer, though both the original NesC and<br />

the OSM implementations only use a single hardware timer <strong>for</strong> all states. The<br />

individual timers are always initialized in transitions, either when entering a<br />

new state through a transition, or after triggering a recurring action within the<br />

current state, that is, in a self transition. Table 7.2 lists the values that are used to<br />

initialize the timer in the EnviroTrack example based on the transition parameters,<br />

namely the target and source states as well as the trigger event. Using this<br />

example, we will now explain how state-bound resources are initialized in both<br />

the event-driven and the state-based programming models and then compare<br />

both approaches.<br />

Target <strong>State</strong> Source <strong>State</strong> Trigger Line No. Timeout Value<br />

NesC OSM<br />

NEW_CANDIDATE<br />

LEADER_CANDIDATE<br />

FOLLOWER<br />

MEMBER<br />

LEADER<br />

RESIGNING_LEADER<br />

FREE join 154 27 wait_random()<br />

NEW_CANDIDATE timeout[else] 106 29 wait_recruit()<br />

NEW_CANDIDATE timeout[...] 111 36<br />

MEMBER timeout 100 36<br />

MEMBER resign[...] 269 36<br />

FREE recruit OR resign 248 12<br />

LEADER_CANDIDATE leave[else] 200 12<br />

FOLLOWER recruit OR resign 256 12<br />

MEMBER leave 186 12<br />

RESIGNING_LEADER recruit OR candidate 337 12<br />

NEW_CANDIDATE recruit 279 19<br />

NEW_CANDIDATE candidat[...] 284 19<br />

LEADER_CANDIDATE recruit[...] 295 19<br />

LEADER_CANDIDATE candidate[...] 300 19<br />

FOLLOWER join 161 19<br />

MEMBER recruit 264 19<br />

LEADER recruit[...] 313 19<br />

wait_random()<br />

wait_threshold()<br />

wait_receive()<br />

LEADER_CANDIDATE timeout 120 45 wait_random()<br />

RESIGNING_LEADER join 169 46 wait_recruit()<br />

LEADER timeout 126 47 — ” —<br />

LEADER recruit[else] 317 47 — ” —<br />

LEADER resign OR candidate 325 47 — ” —<br />

RESIGNING_LEADER timeout[...] 135 56 wait_recruit()<br />

LEADER leave 209 56<br />

Table 7.2: Timeouts in the EnviroTrack group-management protocol are initialized<br />

as part of transitions and predominantly depend on the target<br />

state only. The source states and triggers of self transitions are set in<br />

italics. Line numbers refer to the source code given in Appendices B.1<br />

and B.2 (<strong>for</strong> the NesC and OSM version, respectively). The actual timeout<br />

value is expressed as the return value of a function.<br />

Original NesC Implementation<br />

In the NesC implementation of the EnviroTrack group-management protocol, a<br />

single timer is used to implement timeouts in all states using a count-down approach.<br />

The timer is switched on when the program starts and is never switched<br />

off. It fires at a regular interval that never changes. The corresponding timeout


128 Chapter 7. <strong>State</strong>-based <strong>Programming</strong> in Practice<br />

handler fireHeartBeat() of the NesC implementation is shown in line 80<br />

et seqq. in Appendix B.1. An excerpt of the timeout handler is shown below.<br />

The timeout handler counts down a integer variable (line 5), except when in<br />

the FREE state (line 2). When the count-down variable reaches zero (line 7),<br />

an action is invoked, a transition is triggered, or both. Additionally, the countdown<br />

variable is reset. The code fragment below implements the transition from<br />

FOLLOWER to FREE in the lines 9-13 (also see the state-transition diagram in<br />

Fig. 7.2). This transition is triggered when neither the leader has been heard nor<br />

the target has been detected until the timeout fires.<br />

1 command result_t GroupManagement.fireHeartBeat() {<br />

2 if( _GMStatus.status == FREE )<br />

3 return SUCCESS;<br />

4 if( _generalTimer > 0 )<br />

5 _generalTimer--;<br />

6<br />

7 if( _generalTimer


7.2. Modular and Well-Structured Program Design 129<br />

10 } // end switch<br />

11 } // end command<br />

12<br />

13 task void ProcessRecuritMessage() {<br />

14 GMPacket* RxBuffer;<br />

15 // ...<br />

16 switch( _GMStatus.status ) {<br />

17 case FOLLOWER: { // self transition<br />

18 if( (RxBuffer->type==RECRUIT) || (RxBuffer->type==RESIGN) ) {<br />

19 op14();<br />

20 _generalTimer = wait_threshold();<br />

21 }<br />

22 break;<br />

23 } // end case FOLLOWER<br />

24 // ...<br />

25 } // end switch<br />

26 } // end task<br />

OSM Re-Implementation<br />

In our OSM re-implementation (see Appendix B.1), we also use a single hardware<br />

timer. However, instead of using a fixed-rate timer and a count-down variable,<br />

we directly initialize the timer to the desired timeout value. This reflects<br />

the state-centric view in that an individual timer exists <strong>for</strong> every program state<br />

and saves us from having a global event handler to count down the variable.<br />

So instead of resetting a global count-down variable (as _generalTimer in the<br />

NesC code), we initialize a state-specific timer. In OSM, the timers are initialized<br />

upon state entry in the incoming transition using the reset_timer() function,<br />

as shown in the code fragment below. However, <strong>for</strong> the simplicity and efficiency<br />

of the implementation, we sill use a single timer. This approach is more natural<br />

to the state-based approach of OSM and yields the same results as the NesC<br />

implementation.<br />

The code fragment below shows the timer initializations <strong>for</strong> the FOLLOWER<br />

state in the OSM initialization. The incoming transition denoted by the onEntry<br />

keyword neither specify the transition’s source state nor its trigger. There<strong>for</strong>e,<br />

the action is always per<strong>for</strong>med when the state is entered.<br />

1 state FOLLOWER {<br />

2 onEntry: / reset_timer( wait_threshold() );<br />

3 join / op7() -> MEMBER;<br />

4 timeout / op11() -> FREE;<br />

5 recruit_msg OR resign_msg / op14() -> self; // invokes onEntry actions<br />

6 }<br />

Comparison<br />

When analyzing the code of both implementations with respect to the number<br />

of initializations required, as summarized in Table 7.2, we find that in the<br />

NesC code there are 24 lines in which a timeout initialized compared to 9 individual<br />

lines in OMS. That is, multiple initializations in a particular state in


130 Chapter 7. <strong>State</strong>-based <strong>Programming</strong> in Practice<br />

NesC can be condensed to a single line in OSM, if the initialization procedure is<br />

identical. For example, when entering the FOLLOWER state (from another state<br />

or a self transition) the timer is always initialized to a value returned by the<br />

wait_threshold() function. This can be written in a single line, as in the<br />

code fragment above (line 2).<br />

But even when initializations cannot be condensed because initializations differ<br />

(e.g., the timeout value varies with the transition through which the target<br />

state is entered instead of being strictly state-specific), OSM has a clear benefit.<br />

While in this case NesC initializations <strong>for</strong> a single target state are scattered<br />

throughout much of the program, they are neatly co-located in OSM. For example,<br />

the initializations <strong>for</strong> the LEADER state are per<strong>for</strong>med in lines 120, 126, 168,<br />

316, and 324 in the NesC code, while in OSM they are per<strong>for</strong>med in lines 45, 46,<br />

and 47.<br />

In real-world sensor-node programs, the actual initialization procedures (and<br />

values, as in our timer example) very often depend exclusively on the target state<br />

of the transition. In OSM, these initializations can be easily modeled as incoming<br />

transitions. If the initialization code is equal <strong>for</strong> all transitions to a certain state,<br />

they can be condensed to a single line as in FOLLOWER state already mentioned<br />

above, like this:<br />

onEntry: / reset_timer( wait_threshold() );.<br />

Incoming transitions are specified in the scope of a state description (using the<br />

onEntry keyword). There<strong>for</strong>e, all initialization code is co-located, even if the<br />

concrete initialization depends on transition parameters such as source state,<br />

trigger events, and guards. For example, the timer initializations in the leader<br />

state are per<strong>for</strong>med as shown in the code fragment below in OSM. In eventdriven<br />

programs, however, the initializations of a resource belonging to a particular<br />

state are scattered throughout the program together with the event handlers<br />

that implements transitions to that state.<br />

1 state LEADER {<br />

2 // incoming transitions:<br />

3 onEntry: LEADERCANDIDATE -> / reset_timer( wait_random() );<br />

4 onEntry: RESIGNINGLEADER -> / reset_timer( wait_recruit() );<br />

5 onEntry: self -> / reset_timer( wait_recruit() );<br />

6 // self transitions:<br />

7 timeout / send_recruit_msg() -> self;<br />

8 // more transitions follow...<br />

9 }<br />

In sensor-node programs timers are also often used to implement recurring<br />

operations without state changes, such as finding network neighbors, sampling,<br />

etc. In our example, the recruit message is send regularly in the LEADER state.<br />

In OSM, this can be modeled with an explicit self transition as shown in line 7<br />

of the previous code fragment. The self transition then automatically triggers<br />

the appropriate onEntry action, that is, line 5 in the above code. Where a self<br />

transition does not seem adequate, a regular action without transition can be<br />

used instead, as shown below. Though this requires multiple lines of code even<br />

<strong>for</strong> identical initializations, the code is still part of the definition of a single state.<br />

timeout / reset_timer( wait_threshold() );


7.2. Modular and Well-Structured Program Design 131<br />

7.2.4 Avoiding Accidental Concurrency<br />

Another special case of manual state management is program code to avoid accidental<br />

concurrency. In Sect. 4.1 we stated that accidental concurrency is a source<br />

of error and may lead to corrupted data and deadlock. Accidental concurrency<br />

may occur when a non-reentrant cascade of events is triggered again be<strong>for</strong>e the<br />

previous cascade has completed.<br />

Most event-driven programs may suffer from accidental concurrency and special<br />

measures must be taken by programmers to avoid it. A common pattern<br />

used throughout the many event-driven programs that we have analyzed is to<br />

simply ignore events that would restart a non-reentrant cascade of events. However,<br />

from our own experience we know that potentially harmful situations in<br />

the code are not easily identified or that programmers quite often simply <strong>for</strong>get<br />

to implement countermeasures.<br />

On the other hand, most OSM programs are “by design” not susceptible to<br />

accidental concurrency. In the following we will provide two examples of eventdriven<br />

programs where measures against accidental concurrency have been<br />

taken. The first of the two examples is the Surge application, which we have<br />

already discussed in Section 4.2.1. We will use this small example to explain the<br />

measures typically taken by programmers to avoid accidental concurrency and<br />

why typically no measures are needed in OSM. The second example is again<br />

EnviroTrack. We will analyze the entire EnviroTrack code in order to quantify<br />

the overhead of countermeasures against accidental concurrency.<br />

Our first example is the Surge application taken <strong>for</strong>m the publication presenting<br />

the NesC language [45]. Triggered by a regular timeout, the Surge program<br />

(as shown in Prog. 7.3) samples a sensor and sends the sample off to a predetermined<br />

node.<br />

The order of actions (i.e., NesC tasks and events) in the code of Prog. 7.3 suggest<br />

a linear order of execution that does not exist. Particularly, the timeout<br />

(line 6) may fire again be<strong>for</strong>e sending actually succeeded with an sendDone event<br />

(line 22). There<strong>for</strong>e, the programmer of Surge choose to introduce the busy flag<br />

(line 3) to indicate that the cascade has not been completed (line 9) and that<br />

subsequent timeouts need to be ignored until the flag is reset (line 23).<br />

In OSM state machines, all events that are to trigger a transition or an action<br />

within a particular state need to be specified explicitly. As OSM programmers<br />

would typically explicitly model sequences of events as a state machine, unexpected<br />

events cannot accidentally trigger a cascade of events. Program. 7.4<br />

shows the implementation of Surge in OSM, which does not require an explicit<br />

busy flag. The OSM state machine only reacts to a timeout event while in the<br />

IDLE state (line 3). The program can only be restarted after again entering the<br />

IDLE state, that is, after the sendDone event has occurred (line 19). Intermittent<br />

timeout events are ignored. Alternatively, an error handler could be added to the<br />

BUSY state after line 19, like this:<br />

timeout / error_handler();.<br />

In the previous example, there was only one potential case of accidential concurrency,<br />

which was countered by a single flag that was accessed twice. In<br />

real-world programs, however, more ef<strong>for</strong>t is required to guard against accidental<br />

concurrency. For example, in the EnviroTrack group-management code


132 Chapter 7. <strong>State</strong>-based <strong>Programming</strong> in Practice<br />

Program 7.3: NesC code of the Surge program from [45] (slightly simplified version).<br />

1 module SurgeM { ... }<br />

2 implementation {<br />

3 bool busy;<br />

4 uint16_t sample;<br />

5<br />

6 event result_t Timer.fired() {<br />

7 if( busy ) return FAIL;<br />

8 call ADC.getData();<br />

9 busy = TRUE;<br />

10 return SUCCESS;<br />

11 }<br />

12 event result_t ADC.dataReady( uint16_t data ) {<br />

13 sample = data;<br />

14 post sendData();<br />

15 return SUCCESS;<br />

16 }<br />

17 task void sendData() {<br />

18 adcPacket.data = sample;<br />

19 call Send.send( &adcPacket, sizeof adcPacket.data );<br />

20 return SUCCESS;<br />

21 }<br />

22 event result_t Send.sendDone( TOS_Msg *msg, result_t success ) {<br />

23 busy = false;<br />

24 return SUCCESS;<br />

25 }<br />

26 }<br />

2 boolean flags are used, which are accessed in 16 of the 426 lines, that is, almost<br />

4% of the group-management code. In the entire EnviroTrack implementation<br />

there are 9 boolean flags, which are accessed in 58 lines of code. That is, measures<br />

against accidental concurrency cost 9 bytes of memory and account <strong>for</strong> 3%<br />

of the entire code.<br />

In the previous example, most of the flags are introduced in the processing of<br />

radio messages. Several components that process radio messages have a single<br />

buffer <strong>for</strong> incoming messages only. Thus, a received message has to be processed<br />

completely be<strong>for</strong>e a new one can be accepted into the buffer. Particularly, the<br />

previous message must not be overwritten while being processed. Similar to<br />

the previous Surge example, EnviroTrack simply drops radio messages that are<br />

received while still processing a previous message. The same also applies to<br />

outgoing messages.


7.3. Memory-Efficient Programs 133<br />

Program 7.4: Reimplementation of SURGE in OSM. A graphical representation<br />

of the OSM state machine is depicted to the left.<br />

SURGE<br />

IDLE<br />

BUSY<br />

W4_DATA<br />

W4_SEND<br />

W4_ACK<br />

timeout<br />

dataReady<br />

sendData<br />

sendDone<br />

1 state SURGE {<br />

7.3 Memory-Efficient Programs<br />

2 initial state IDLE {<br />

3 timeout/ ADC_getData() -> BUSY;<br />

4 }<br />

5 state BUSY {<br />

6 uint16_t sample;<br />

7<br />

8 initial state W4_DATA {<br />

9 dataReady/<br />

10 sample=dataReady.data,<br />

11 post( sendData ) -> W4_SEND;<br />

12 }<br />

13 state W4_SEND {<br />

14 sendData/ send(sample)->W4_ACK;<br />

15 }<br />

16 state W4_ACK {<br />

17 // do nothing<br />

18 }<br />

19 sendDone/ -> IDLE;<br />

20 } // end BUSY<br />

21 } // end SURGE<br />

In the previous section we have shown that partitioning sensor-node programs<br />

in the time domain through explicitly modeled states notation benefits modularity<br />

and structure of the program code. In this section we show that OSM’s<br />

concept of state variables—variables the lifetime of which are associated with<br />

states—can also benefit memory efficiency.<br />

The basic idea of saving memory is to allocate memory only <strong>for</strong> those variables,<br />

that are required in the currently active state of the program. When the<br />

program state changes, the memory used by variables of the old program state<br />

is automatically reused <strong>for</strong> the variables of the new state. In our state-based approach,<br />

states not only serve as a structuring mechanism <strong>for</strong> the program, they<br />

also serve as a scoping and lifetime qualifier <strong>for</strong> variables. Regular event-driven<br />

programming models lack this abstraction and thus <strong>for</strong>ce programmers to use<br />

global variables with global lifetime, as described in Sect. 4.1.<br />

Using an example based on the program to calculate the average of remote<br />

temperature sensors from Sect. 4.1, we now show how the state-based programming<br />

model can be used in practice to save memory by storing temporary data<br />

in state variables. Then we answer the question how this technique is generally<br />

applicable to sensor-node programming and how much memory can be saved<br />

in real-world programs.


134 Chapter 7. <strong>State</strong>-based <strong>Programming</strong> in Practice<br />

7.3.1 Example<br />

In the previous sections of this chapter we have always referred to the EnviroTrack<br />

group-management protocol <strong>for</strong> comparing the event-driven and the<br />

state-based programming approaches. This example, however, is not suitable to<br />

demonstrate OSM’s memory efficiency. While the protocol can be nicely specified<br />

as a set of distinct states, individual states do not have temporary data<br />

attached exclusively to them. (However, they have another resource associated<br />

to them, namely timers, which we have discussed in the section about accidental<br />

concurrency.)<br />

There<strong>for</strong>e, we take up the small toy example from Sect. 4.1 again, which we<br />

used to explain the shortcomings of event-driven programming. The original,<br />

event-driven program (see Prog. 4.1) calculates the average of temperature values<br />

from remote sensors in the vicinity. Program 7.5 depicts the equivalent program<br />

in OSM as the superstate REMOTE_AVERAGE. Just as the original version<br />

of the program, the OSM code of REMOTE_AVERAGE has two variables num and<br />

sum, which store the number of remote samples received and the sum of all temperature<br />

values, respectively. These variables are temporary in nature, as they<br />

are only required until the average is computed when the timeout occurs. In<br />

the original, event-driven program, these variables are global, thus taking up<br />

memory even when they are no longer needed. In OSM, on the other hand, the<br />

variables are modeled as state variables. The variables memory are automatically<br />

reused by the OSM compiler <strong>for</strong> state variables of subsequent states.<br />

Program 7.5: OSM equivalent to the event-driven Program 4.1. In this version,<br />

the variables num and sum are modeled as state variables instead<br />

of global variables.<br />

REMOTE_AVERAGE<br />

int num = 0, sum = 0;<br />

extern int avg;<br />

/init_remote_average()<br />

RECEIVING<br />

DONE<br />

message_ev /<br />

message_hdl(num, sum)<br />

timeout_ev<br />

/ timeout_hdl(num, sum, avg)<br />

1 state REMOTE_AVERAGE {<br />

2 var int num = 0, sum = 0;<br />

3 extern var int avg;<br />

4 onEntry:/init_remote_average();<br />

5 initial state RECEIVING {<br />

6 message_ev /<br />

7 message_hdl(num, sum);<br />

8 timeout_ev /<br />

9 timeout_hdl(num, sum, avg )<br />

10 -> DONE;<br />

11 }<br />

12 state DONE {}<br />

13 }<br />

Program 7.6 shows an example where REMOTE_AVERAGE is used in conjuncton<br />

with two other states. In this program, the node’s local sensor is<br />

calibrated based on the average of remote temperatures (as calculated by<br />

REMOTE_AVERAGE) and the average of a series of local temperature readings.<br />

The local average is computed in the LOCAL_AVERAGE state. Its implementation<br />

(not shown here) is very similar to that of REMOTE_AVERAGE. Particularly,


7.3. Memory-Efficient Programs 135<br />

it also has two state variables num and sum with the same meaning. Only instead<br />

of collecting temperature values from remote message events, the temperature<br />

values are collected from local sensor events. The actual calibration is per<strong>for</strong>med<br />

in ADJUST. We make no assumption about the implementation of this state.<br />

Program 7.6: OSM program <strong>for</strong> calibrating a local sensor using the averages of<br />

remote and local sensors.<br />

CALIBRATION<br />

int ravg=0, lavg=0;<br />

REMOTE_AVERAGE<br />

int num=0, sum=0;<br />

...<br />

LOCAL_AVERAGE<br />

int num=0, sum=0;<br />

...<br />

ADJUST<br />

...<br />

e1<br />

e2<br />

14 state LOCAL_AVERAGE { /*...*/ }<br />

15<br />

16 state CALIBRATION {<br />

17 var int ravg = 0, lavg = 0;<br />

18 initial state<br />

19 incarnate REMOTE_AVERAGE (<br />

20 int ravg as avg ) {<br />

21 e1 / -> LOCAL_AVERAGE;<br />

22 }<br />

23 initial state<br />

24 incarnate LOCAL_AVERAGE (<br />

25 int lavg as avg ) {<br />

26 e2 / -> ADJUST;<br />

27 }<br />

28 state ADJUST { /*...*/ }<br />

29 }<br />

In this simple yet realistic example, the two subsequent states<br />

REMOTE_AVERAGE and LOCAL_AVERAGE both have local data stored in<br />

state variables. Because the states are composed sequentially, their state<br />

variables are mapped to an overlapping memory region, effectively saving<br />

memory. In this particular case the state variables of both states are of the<br />

exact same types and sizes. Thus the state variables of the subsequent state<br />

would be mapped to the exact memory locations of those of the previous state.<br />

There<strong>for</strong>e, the memory saved (with respect to the event-driven approach, where<br />

all variables are global) is half of the total memory, that is, the size of two<br />

integers, typically 4 bytes on 8-bit microcontrollers.<br />

On the other hand, the given OSM implementation introduces two additional<br />

variables, namely ravg and lavg. They are used to store the remote and local<br />

averages until the actual calibration has completed. Indeed, using state variables<br />

to share data among substates is a common pattern in OSM. The shared<br />

data typically represents results of computations that are per<strong>for</strong>med in one state<br />

and need to be accessed in subsequent states, as in the previous example. The<br />

requirement of additional variables is a direct consequence of the limited scope<br />

and limited lifetime of state variables. They would not be required if all variables<br />

where global. There<strong>for</strong>e, one could object that those additional variables<br />

would cancel out the memory savings gained by state variables with respect to<br />

the event-driven approach. In general, however, the temporary data required to<br />

compute a result is larger than the result itself. There<strong>for</strong>e, the saving typically


136 Chapter 7. <strong>State</strong>-based <strong>Programming</strong> in Practice<br />

only reduced by a small amount. Furthermore, CALIBRATION may be an only<br />

transient state itself. Then its (as well as its substate’s) memory resources would<br />

also reused once the state is left. Since CALIBRATION does not compute a result<br />

required in a subsequent state, no additional variables are needed.<br />

Since the OSM compiler compiles OSM program specifications back to regular<br />

event-driven code, one could also object that the mechanism of mapping<br />

variables to overlapping memory regions could also be used manually in the<br />

plain event-driven model. Though possible in principle, programmers would<br />

not only have to manage the program’s state machine manually. They would<br />

also have to manually map the states’ variables to the complex memory representation,<br />

such as the one shown in Prog. 6.5. Even minor changes to states high<br />

in the hierarchy, such as the addition or removal of a layer in the hierarchy, lead<br />

to major modifications of the memory layout. Consequentially, access to elements<br />

of the structure would also change with the structure, requiring changes<br />

throughout the program. There<strong>for</strong>e we think that our state-variable approach is<br />

only manageable with the help of a compiler.<br />

7.3.2 General Applicability and Efficiency<br />

Now two question arises: are state variables applicable to real-world sensornode<br />

programs? And, how much memory savings can we expect? We cannot<br />

answer these questions conclusively, as extensive application experience is missing.<br />

However, we have several indications from our own work with sensor-node<br />

programming as well as from descriptions of sensor-node algorithms in the literature<br />

that state variables are indeed useful.<br />

General Applicability of <strong>State</strong> Variables<br />

Probably the most-generally applicable example where sensor-node programs<br />

are structured into discrete phases of operation is initialization and setup versus<br />

regular operation of a sensor node. Typically sensor nodes need some <strong>for</strong>m<br />

of setup phase be<strong>for</strong>e they can engage in their regular operation mode. Often<br />

longer phases of regular operation regularly alternate with short setup phase.<br />

Both phases typically require some amount of temporary data that can be released<br />

once the phase is complete. Setup may involve assigning roles to individual<br />

nodes of the network [56, 98], setting up a network topology (such as<br />

spanning trees [84]), per<strong>for</strong>ming sensor calibration, and so on. Once setup is<br />

complete, the node engages in regular operation, which typically involves tasks<br />

like sampling local sensors, aggregating data, and <strong>for</strong>warding messages. Regular<br />

operation can also mean to per<strong>for</strong>m one out of several roles that has been<br />

assigned during setup. For the setup phase, temporary data may contain a list<br />

of neighboring nodes with an associated measure of link quality, node features,<br />

as well as other properties of the node <strong>for</strong> role assignment and topology setup.<br />

Or they may contain a series of local and remote sensor measurements <strong>for</strong> calibration<br />

(as in our previous example). For regular operation, such variables may<br />

contain aggregation data, messages to be <strong>for</strong>warded, and so on.


7.3. Memory-Efficient Programs 137<br />

Effective Memory Savings<br />

In theory, state variables can lead to very high memory savings. As we explained<br />

earlier, the memory consumption of state variables equals the maximum of the<br />

memory of variable footprints among all distinct states. The memory consumption<br />

of the same data stored in global variables equals the sum of the memory<br />

footprints. For example, in a program with 5 sequential states each using the<br />

same amount of temporary data, state variables only occupy 1/5-th of the memory<br />

of global variables, yielding savings of 80%.<br />

However, the effective savings that can be achieved in real-world programs<br />

will be significantly lower because of three main reasons. Firstly, often at least<br />

some fraction of a program’s data is not of temporary nature but is indeed required<br />

throughout the entire runtime of the software. In OSM, such data would<br />

be modeled as state variables associated to the top-level state, making them effectively<br />

global. With the state-variable approach, no memory can be saved on<br />

global data.<br />

Secondly, we found that sometimes the most memory efficient state-machine<br />

representation of a given algorithm may not be the most practical or the most<br />

elegant. For the purpose of saving memory, a state machine with several levels<br />

of hierarchy is desirable. On the other hand, additional levels of hierarchy can<br />

complicate the OSM code, particularly when it leads to transitions crossing hierarchy<br />

levels. As a consequence, programmers may not always want to exploit<br />

the full savings possible with state variables.<br />

The final and perhaps most important reason is that in typical programs a<br />

few states exceed others in terms of memory consumption by far. It is these<br />

dominant states that determine the total memory consumption of an OSM statemachine<br />

program. Examples are phases during which high-bandwidth sensor<br />

data (such as audio and acceleration) is buffered and processed (e.g., [12, 117])<br />

or phases during which multiple network messages are aggregated (e.g., [84]).<br />

<strong>State</strong> variables do not help reducing the memory requirements of memoryintensive<br />

states. For these states, programmers still have the responsibility to<br />

use the available resources economically and to fine tune the program accordingly.<br />

The data state (i.e., memory) used by each of the non-dominant states, however,<br />

“falls into” the large chunk of memory assigned to the dominant state and<br />

is thus effectively saved. A positive side effect of this mechanism is that in most<br />

states (i.e., the ones that are less memory intensive) programmers can use variables<br />

much more liberally, as long as the total per-state memory is less than the<br />

memory of the dominant state. Since the per-state memory requirements are<br />

calculated at compile time, programmers get an early warning when they have<br />

been using memory too liberally.<br />

Due to the reasons explained above we consider savings in the range of 10%<br />

to 25% to be realistic <strong>for</strong> most programs, even without fine tuning <strong>for</strong> memory<br />

efficiency. The savings that can be effectively achieved by switching from<br />

an event-driven programming framework to state-based OSM, however, largely<br />

depends on the deployed algorithms as well as the individual programming<br />

style.


138 Chapter 7. <strong>State</strong>-based <strong>Programming</strong> in Practice<br />

7.4 Summary<br />

In this chapter we have illustrated how the state-based programming approach<br />

of OSM compares to the “traditional” event-driven approach. In particular, we<br />

have shown how largely automating state and stack management in OSM benefits<br />

programmers of real-world sensor-node programs.<br />

We have started with an abstract yet intuitive motivation of OSM’s main benefits.<br />

We have illustrated that event-driven programs have an equivalent OSM<br />

representation, which is trivial in the sense that it only consists of a single state.<br />

The trivial state machine can then be refined with multiple states and state hierarchies,<br />

directly improving its structure and modularity. The resulting statemachine<br />

representation can also increase the program’s memory efficiency by<br />

using state variables, which provide memory reuse without programmer intervention.<br />

The reverse trans<strong>for</strong>mation cannot be done without loosing those<br />

benefits again.<br />

In the second section of this chapter we have taken an experimental approach<br />

in showing the positive effects of OSM’s automated state management. We have<br />

re-implemented a significant part of a large program that was originally implemented<br />

in the event-driven NesC programming framework. Then we have<br />

compared both approaches. In the example, we have shown that the tedious and<br />

error-prone code <strong>for</strong> state management could be reduced by 70% (from 187 to<br />

56 lines of code), leading to an overall program-size reduction of 31%. A special<br />

case of manual state management is extra program code needed to avoid accidental<br />

concurrency in event-driven programs. In the example, this extra code<br />

accounts <strong>for</strong> 3% of the entire program. We could show that in OSM typically no<br />

countermeasures are required at all.<br />

Finally, we have shown that the state-based approach of OSM has two major<br />

benefits over the event-driven code when it comes to initializing resources.<br />

The fist benefit is that all initializations of the same state-based resource are colocated<br />

(within the scope of an OSM state), rather then being scattered throughout<br />

the program code. The second benefit is that equal initializations (e.g., to the<br />

same value) need to be written only once. As a result, the code is more concise,<br />

more readable and less error prone.<br />

The chapter ends with a section on how memory efficiency can be achieved<br />

through state variables. Though quantitative measurements with respect to<br />

memory savings are currently still missing, we have illustrated that savings of<br />

10 to 25% are realistic <strong>for</strong> typical sensor-node programs.


8 Related Work<br />

In this chapter we survey approaches and techniques related to sensor-node programming<br />

in general, as well as to OSM and its implementation in particular. In<br />

the introduction of this dissertation we have described wireless sensor nodes as<br />

networked embedded systems. In Section 8.1 we present a brief overview on<br />

the state of the art in embedded-systems programming. We also try to answer<br />

the question why these approaches have been largely neglected in the wireless<br />

sensor-network community. Many of the approaches to embedded-systems programming<br />

are based on finite state machines. These approaches are particularly<br />

relevant to our work. We will discuss them in Section 8.2 and contrast them<br />

with our work. In Chapter 3 we have already presented a number of programming<br />

models and frameworks targeted particularly towards sensor nodes. In<br />

Section 8.3 we will revisit some of them in order to compare their goals and solution<br />

approaches to OSM. Though many of them share the same goals as OSM,<br />

the approaches to achieving those goals typically differ significantly.<br />

8.1 <strong>Programming</strong> Embedded Systems<br />

The diversity of embedded systems is reflected in the number and diversity of<br />

models and frameworks available <strong>for</strong> programming them. The majority of them<br />

is, however, programmed using conventional methods also found in programming<br />

of general-purpose computers. In this section we will first discuss the conventional<br />

programming methods <strong>for</strong> embedded systems be<strong>for</strong>e turning to the<br />

large number of domain-specific programming frameworks addressing particular<br />

requirements of a domain. <strong>Sensor</strong> nodes are subject to some of the addressed<br />

requirements. There<strong>for</strong>e such programming frameworks and models are very<br />

relevant <strong>for</strong> wireless sensor-node programming and OSM is based heavily on<br />

the concepts found in them. However, besides OSM we are not aware of any<br />

other programming frameworks <strong>for</strong> wireless sensor nodes that draw from such<br />

approaches.<br />

8.1.1 Conventional <strong>Programming</strong> Methods<br />

<strong>Programming</strong> Language<br />

The available programming support <strong>for</strong> a systems crucially depends on its available<br />

system resources. So does the supported programming language. The majority<br />

of embedded systems today is programmed in the general-purpose programming<br />

language C. C is very close to the computational model implemented<br />

in today’s microcontrollers. Thus there only needs to be a thin adaptation layer<br />

resulting in very resource-efficient implementations of C applicable to very resource<br />

constrained systems. Often C is used in combination with assembly lan-


140 Chapter 8. Related Work<br />

guage. A point often made in favor of using assembly language is the full access<br />

to the processor’s registers and instruction set, thus allowing to optimize <strong>for</strong><br />

speed. On the other hand, C is considered easy and fast to program as well<br />

as more portable between different processors. The portability of C also fosters<br />

code reuse and companies often have a large code base which they share among<br />

several systems and projects. While C compilers are available <strong>for</strong> almost all embedded<br />

systems, programming languages like C++ and Java are only available<br />

<strong>for</strong> systems with ample resources. The reason is that those languages require a<br />

thicker adaptation layer and thus occupy more system resources.<br />

System and <strong>Programming</strong> Support<br />

Today, embedded systems on the high end of the resource spectrum use programming<br />

methods very similar to those used <strong>for</strong> general-purpose computing<br />

plat<strong>for</strong>ms. Particularly devices such as mobile phones and PDAs have<br />

turned into general-purpose computing plat<strong>for</strong>ms much more resembling desktop<br />

computers than embedded systems. Indeed, some of today’s embedded<br />

systems posses resources that are similar to those of high-end desktop systems<br />

less than a decade ago. At that time, general-purpose operating systems had<br />

already pretty much evolved into what they are now. There<strong>for</strong>e, it is not surprising<br />

that the operating systems found in high-end embedded systems today<br />

also support features of state-of-the-art operating systems, including dynamically<br />

loadable processes, multi-threading, and dynamic memory management<br />

with memory protection. There are several such operating systems available<br />

from the open-source domain as well as commercially. The most popular of<br />

such systems may be Windows CE, Embedded Linux, PalmOS, VxWorks, and<br />

Symbian OS. However, the programming techniques supported by these operating<br />

systems are not applicable to the majority of embedded systems and are<br />

particularly ill-suited <strong>for</strong> very resource-constrained sensor nodes.<br />

Systems with resources on the medium to low end of the spectrum typically<br />

only have limited system support from a light-weight operating environment<br />

or use no operating system at all. Light-weight embedded operating systems<br />

are very similar to those found in wireless sensor networks and may have been<br />

their archetypes. They typically do not provide system support such as dynamic<br />

memory, loadable processes, etc. Many of them, however, provide a multithreaded<br />

execution environment.<br />

As in WSNs, embedded systems at the very low end of the resource spectrum<br />

use no operating systems or one providing event-based run-to-completion<br />

semantics. With no operating system support, the software is typically implemented<br />

as a control loop, which we have already discussed in the context of<br />

sensor nodes in Sect. 3.2. As we have discussed extensively in Chapter 4, eventbased<br />

approaches are not sufficient to program complex yet reliable sensor-node<br />

applications with a high degree of concurrency and reactivity.<br />

8.1.2 Domain-specific <strong>Programming</strong> Approaches<br />

Due to their stringent requirements some specific embedded-systems applications<br />

and domains require other than the conventional programming meth-


8.1. <strong>Programming</strong> Embedded Systems 141<br />

ods. In particular, high demands regarding the reliability of inherently complex<br />

and highly concurrent programs call <strong>for</strong> design methodologies that allow<br />

to use high-level design tools, automated testing, simulation, <strong>for</strong>mal verification,<br />

and/or code synthesis (i.e., automated code generation). Conventional<br />

programming models fail in these domains as they are highly indeterministic<br />

and make large-scale concurrency hard to control <strong>for</strong> the programmer. Additionally,<br />

they make reasoning about the memory requirements of a program very<br />

hard, particularly if combined with dynamic memory management. As conventional<br />

programming approaches do not lend themselves easily to the procedures<br />

described above, new models of computation have been suggested and incorporated<br />

into design tools. However, the available frameworks and tools supporting<br />

such models have been very small in number compared with the more ad<br />

hoc designs [77].<br />

Originally, embedded systems have been classified according to their application<br />

characteristics into control-oriented systems on the one hand and dataoriented<br />

systems on the other hand. However, in recent years there have been<br />

several ef<strong>for</strong>ts to combine the approaches as embedded systems often expose<br />

characteristics of both categories. Today a few powerful tools exist supporting<br />

both approaches in a unified model. To explain the principal approaches, we<br />

retain the original classification.<br />

Data-Oriented Systems<br />

Data-oriented systems concentrate on the manipulation of data. They are often<br />

called trans<strong>for</strong>mative as they trans<strong>for</strong>m blocks or streams of data. Examples<br />

<strong>for</strong> trans<strong>for</strong>mative systems are digital signal processors <strong>for</strong> image filtering as<br />

well as video and audio encoding, which can be found in medical systems, mobile<br />

phones, cameras and so on. Since data-processing typically takes a nonnegligible<br />

time, data-oriented programming frameworks typically need to address<br />

real-time requirements.<br />

A common meta model <strong>for</strong> describing data-oriented systems are dataflow<br />

graphs and related descriptions. Such systems are composed of functional units<br />

that execute only when input data is available. Data items (also called tokens)<br />

are considered atomic units; their flow is represented by directed graphs where<br />

each node represents a computational operation and every edge represents a<br />

datapath. Nodes connected by a directed edge can communicate by sending<br />

data items. Nodes consume the data items received as input and produce new<br />

data as output. That is, nodes per<strong>for</strong>m trans<strong>for</strong>mational functions on the data<br />

items received. Edges may serve as buffers of fixed or (conceptually) unlimited<br />

sizes, such as unbounded FIFO queues. Dataflow descriptions are a natural representation<br />

of concurrent signal processing applications. There are a number<br />

of concrete models based on the dataflow meta model. They differ, <strong>for</strong> example,<br />

in the number of tokens an edge may buffer, whether communication is<br />

asynchronous or rendezvous, and whether the number of tokens consumed and<br />

produced in an execution is specified a priori.


142 Chapter 8. Related Work<br />

Control-Oriented Systems<br />

Instead of computing outputs data from input data, control-oriented embedded<br />

systems typically control the device they are embedded into. Often controloriented<br />

systems constantly react to their environment by computing output<br />

events from input events (i.e., stimuli from outside of the embedded system).<br />

Such systems are called reactive. Examples of reactive systems are protocol processors<br />

in fax machines as well as mobile phones, control units in automobiles<br />

(such as airbag release and cruise control), and fly-by-wire systems.<br />

A subcategory of reactive systems are interactive systems, which also react to<br />

stimuli from the environment. However, in interactive systems the computer<br />

is the leader of the interaction, stipulating its pace, its allowable input alphabet<br />

and order, etc. There<strong>for</strong>e, the design of a purely interactive system is generally<br />

considered easier than the design of an environmentally-driven reactive system.<br />

Many of the recent applications of embedded systems consist of both, controloriented<br />

and data-oriented parts. In general, the reactive part of the system<br />

controls the computationally intense trans<strong>for</strong>mative operations. In turn, results<br />

of the such operations are often feed back into the control-oriented part. Thus,<br />

actual embedded systems often expose both characteristics.<br />

Control and Data-oriented <strong>Wireless</strong> <strong>Sensor</strong> <strong>Networks</strong><br />

<strong>Wireless</strong> sensor nodes, too, include trans<strong>for</strong>mational as well as reactive parts.<br />

For example, trans<strong>for</strong>mational parts in sensor nodes compute from streams of<br />

data whether a product has been damaged [106] (acceleration data), the location<br />

where a gun has been fired [86] (audio data), the specific type of animal based on<br />

its call [64, 105, 117, 118] (again, audio data), and so on. The same sensor nodes<br />

also have control-oriented parts, <strong>for</strong> example, <strong>for</strong> processing network-level and<br />

application-level protocols (such as the Bluetooth protocol stack used in [106]<br />

and the EnviroTrack group-management application protocol discussed in the<br />

previous chapter). In WSNs, the feedback loop between the reactive and trans<strong>for</strong>mative<br />

parts is of particular importance. Due to the constrained nature of<br />

the nodes, resource-intense operations are often controlled by criteria that are<br />

themselves computed by a much less intense processing stage. In [118], <strong>for</strong> example,<br />

recorded audio samples are first rated how well they match calls of specific<br />

birds using a relatively simple algorithm. Only if there is a high confidence<br />

that a sample represents a desired birdcall, the samples are compressed and sent<br />

to the clusterhead in order to apply beam<strong>for</strong>ming <strong>for</strong> target localization, which<br />

are very resource-intense operations. Other criteria <strong>for</strong> controlling the behavior<br />

of sensor nodes may reflect the importance of a detection, the proximity (and<br />

thus expected sensing quality) of a target, the number of other nodes detecting<br />

the same target, and so on. In the extreme case, so called adaptive-fidelity<br />

algorithms allow to dynamically trade the quality of a sensing result against resource<br />

usage by controlling the amount of sampling and processing per<strong>for</strong>med<br />

by a node.


8.2. <strong>State</strong>-<strong>Based</strong> <strong>Model</strong>s of Computation 143<br />

8.1.3 Embedded-Systems <strong>Programming</strong> and <strong>Sensor</strong> Nodes<br />

The work by the embedded systems community on techniques such as real-time<br />

systems, model driven design, hardware-software co-design, <strong>for</strong>mal verification,<br />

code synthesis, and others share many goals with WSN research and has<br />

led to a variety of novel programming models and frameworks. Despite their<br />

apparent relevance, alternatives to these approaches from the embedded systems<br />

community seem to have attracted little attention in the wireless sensor<br />

network community. The most popular programming frameworks <strong>for</strong> WSNs<br />

adopt the prevalent programming paradigm: a sequential computational model<br />

combined with a thread-based or event-based concurrency model.<br />

We have no comprehensive answer why this is so. We can only speculate that<br />

the sequential execution model inherent to the most popular general-purpose<br />

programming languages (such as C, C++, Java) has so thoroughly pervaded the<br />

computer-science culture that we tend to ignore other approaches (cf. [77]). Despite<br />

the potential alternatives, even the majority of traditional embedded systems<br />

is still programmed using the classical embedded programming language<br />

C, and recently also C++ and Java. One reason may be that there seems to be<br />

little choice in the commercial market. The most popular embedded operating<br />

systems fall in this domain. Another reason may be that the vast majority of<br />

programmers has at least some experiences in those languages whereas domainspecific<br />

embedded frameworks are mastered only by a selected few. Consequently,<br />

many embedded software courses toughed at universities also focus<br />

on those systems (see, <strong>for</strong> example, [92]). In the next section we will discuss<br />

concrete programming models <strong>for</strong> embedded systems that are most relevant to<br />

OSM.<br />

8.2 <strong>State</strong>-<strong>Based</strong> <strong>Model</strong>s of Computation<br />

OSM draws directly from the concepts found in specification techniques<br />

<strong>for</strong> control-oriented embedded systems, such as finite state machines, <strong>State</strong>charts<br />

[53], its synchronous descendant SyncCharts [10, 11], and other extensions.<br />

From <strong>State</strong>charts, OSM borrows the concept of hierarchical and parallel<br />

composition of state machines as well as the concept of broadcast communication<br />

of events within the state machine. From SyncCharts we adopted the<br />

concept of concurrent events.<br />

Variables are typically not a fundamental entity in control-oriented statemachine<br />

models. Rather, these models rely entirely on their host languages <strong>for</strong><br />

handling data. <strong>Model</strong>s that focus both on the trans<strong>for</strong>mative domain and the<br />

control-oriented domain typically include variables. However, we are not aware<br />

of that any of these models uses machines states as a qualifier <strong>for</strong> the scope and<br />

lifetime of variables.<br />

8.2.1 <strong>State</strong>charts<br />

The <strong>State</strong>charts model lacks state variables in general and thus does not define<br />

an execution context <strong>for</strong> actions in terms of data state. Also, the association<br />

of entry and exit actions with states and transitions, that is, distinct incoming


144 Chapter 8. Related Work<br />

and outgoing actions, is a distinctive feature of OSM. These associations clearly<br />

define the execution context of each computational action to be exactly one program<br />

state and the set of state variables in the scope of that state. While the<br />

<strong>State</strong>charts model, too, has entry and exit actions, they are only associated with<br />

a state and are invoked whenever that state is entered or left, respectively.<br />

8.2.2 UML <strong>State</strong>charts<br />

The Unified <strong>Model</strong>ing Language (UML) [49] is a graphical language <strong>for</strong> specifying,<br />

visualizing, and documenting various aspects of object-oriented software.<br />

UML is composed of several behavioral models as well as notation guides and<br />

UML <strong>State</strong>charts is one of them. UML <strong>State</strong>charts are a combination of the original<br />

<strong>State</strong>charts model and some object-oriented additions. They are intended <strong>for</strong><br />

modeling and specifying the discrete behavior of classes. UML is standardized<br />

and maintained by the Object Management Group (OMG). A number of additions<br />

to UML <strong>State</strong>charts have been proposed <strong>for</strong> various application domains.<br />

UML <strong>State</strong>charts are supported by several commercial tools, <strong>for</strong> example, VisualSTATE<br />

[121]. VisualSTATE has introduced typed variables as an extension to<br />

UML <strong>State</strong>charts, however, all variables are effectively global. Another commercial<br />

tool supporting UML <strong>State</strong>charts is Borland Together.<br />

8.2.3 Finite <strong>State</strong> Machines with Datapath (FSMD)<br />

Finite <strong>State</strong> Machines with Datapath (FSMD) [44] are similar to FSM (neither hierarchical<br />

nor parallel). Variables have been introduced in order to reduce the<br />

number of states that have to be declared explicitly. Like OSM, this model allows<br />

programmers to choose to specify program state explicitly (with machine<br />

states) or implicitly with variables. As in OSM, transitions in FSMD may also<br />

depend on a set of internal variables. FSMD are flat, that is, they do not support<br />

hierarchy and concurrency, and variables have global scope and lifetime.<br />

On the Contrary, variables in OSM are bound to a state hierarchy. The FSMD<br />

model is used <strong>for</strong> hardware synthesis (generating hardware from a functional<br />

description).<br />

8.2.4 Program <strong>State</strong> Machines (PSM), SpecCharts<br />

The Program-<strong>State</strong> Machine (PSM) meta model [114] is a combination of hierarchical<br />

and concurrent FSMs and the sequential programming language model<br />

where leave states may be described as an arbitrarily complex program. Instantiations<br />

of the model are SpecCharts [114] and SpecC [43].<br />

In PSM a program state P basically consists of program declarations and a<br />

behavior. Program declarations are variables and procedures, whose scope is P<br />

and any descendants. A behavior is either a sequence of program statements<br />

(i.e., a regular program) without any substates, or a set of program concurrently<br />

executing substates (each having its own behavior), or a set of sequentiallycomposed<br />

substates and a set of transitions. Transitions have a type and a<br />

condition. Transitions are triggered by a condition represented by a boolean<br />

expression. The transition type is either transition-on-completion (TOC) or


8.2. <strong>State</strong>-<strong>Based</strong> <strong>Model</strong>s of Computation 145<br />

transition-immediately (TI). TOC transitions are taken if and only if the source<br />

program state has finished its computation and condition evaluates to true. TI<br />

transitions are taken when the condition evaluates to true, that is, they terminate<br />

the source state immediately, regardless of whether the source state has finished<br />

its computations. TI transitions can be thought of as exceptions.<br />

Similar to OSM, variables are declared within states; the scope of a variable<br />

then is the state it has been declared in and any descendants. The lifetime of<br />

variables, however, is not defined. The main difference to OSM is, that computations<br />

in SpecCharts are not attached to transitions but rather to leaf states (i.e.,<br />

uncomposed states) only. In analogy to Moore and Mealy machines, we believe<br />

that reactive systems can be specified more concisely in OSM. Though both models<br />

are computationally equivalent, converting a Mealy machine (where output<br />

functions are associated with transitions) to a Moore machine (output associated<br />

with states) generally increases the size of the machine, that is, the number<br />

of states and transitions. The reverse process leads to fewer states. Finally, in<br />

contrast to PSMs, OSM allows to access the values of events in computational<br />

actions. A valued event is visible in the scope of both the source and the target<br />

state of a transition (in “out” and “in” actions, respectively). This is an important<br />

aspect of OSM.<br />

SpecCharts [114] are based on the Program-<strong>State</strong> Machine (PSM) meta model<br />

using VHDL as programming language. It can be considered as a textual statemachine<br />

extension to VHDL. SpecCharts programs are translated into plain<br />

VHDL using an algorithm described in [93]. The resulting programs can then<br />

be subjected to simulation, verification, and hardware synthesis using VHDL<br />

tools.<br />

8.2.5 Communicating FSMs: CRSM and CFSM<br />

A model <strong>for</strong> the design of mixed control and data-oriented embedded systems<br />

are communicating FSMs, which conceptually separate data and control flow. In<br />

this model, a system is specified as a finite set of FSMs and data channels between<br />

pairs of machines. FSM execute independently and concurrently but communicate<br />

over typed channels. Variables are local to a single machine, but global<br />

to the states of that machine. Values communicated can be assigned to variables<br />

of the receiving machine. There are several variations of that basic model. For<br />

example, in Communicating Real-Time <strong>State</strong> Machines (CRSM) [104] communication<br />

is synchronous and unidirectional. Individual FSMs are flat. Co-design<br />

Finite <strong>State</strong> Machines (CFSM) [13] communicate asynchronously via single element<br />

buffers, but FSM may be composed hierarchically. In contrast to communicating<br />

FSMs, concurrent state machines in OSM communicate through events<br />

or shared variables.<br />

8.2.6 Esterel<br />

OSM, like SyncCharts, is implemented on top of Esterel [26]. We considered<br />

using Esterel directly <strong>for</strong> the specification of control flow in OSM. However, as<br />

an imperative language, Esterel does not support the semantics of FSM directly.<br />

We believe that FSM are a very natural and powerful means to model WSN


146 Chapter 8. Related Work<br />

applications. Moreover, specifications in Esterel are generally larger (up to 5<br />

times) compared to OSM.<br />

8.2.7 Functions driven by state machines (Fun<strong>State</strong>)<br />

Fun<strong>State</strong> [110] (functions driven by state machines) is a design meta model <strong>for</strong><br />

mixed control and data flow. Fun<strong>State</strong> was designed to be used as an internal<br />

representation model used <strong>for</strong> verification and scheduling and has no specific<br />

host language.<br />

Fun<strong>State</strong> unifies several well-known models of computation, such as communicating<br />

FSMs, dataflow graphs, and their various incarnations. The model is<br />

partitioned in a purely reactive part (state machine) without computations and<br />

a passive functional part (a “network” resembling PetriNets). Transitions in the<br />

state-machine part trigger functions in the network. The network consists of a<br />

set of storage units, a set of functions and a set of directed edges. Edges connect<br />

functions with storage units and storage units with functions. Edges represent<br />

the flow of valued data tokens.<br />

Fun<strong>State</strong> has a synchronous model of time where in an atomic step tokens<br />

are removed from storage units, computations are executed, and new tokens<br />

are added to storage units. Real time properties of a system can be described<br />

with timed transitions and time constraints. Functions have run-to-completion<br />

semantics; they are executed in non-determinate order.<br />

8.3 <strong>Sensor</strong>-Node <strong>Programming</strong> Frameworks<br />

We have presented the state-of-the-art system software in Chapter. 3. In this<br />

section we will revisit some of them focusing on systems that share the same<br />

goals as OSM or use a programming model related to state machines.<br />

8.3.1 TinyOS<br />

The design of TinyOS shares two main common goals with OSM, namely to<br />

provide efficient modularity as well as to provide a light-weight execution environment,<br />

particularly in terms of memory consumption.<br />

Modularity<br />

Just as OSM, TinyOS has been designed with a high degree of modularity in<br />

mind. The designers argue that sensor-node hardware will tend to be application<br />

specific rather than general purpose because of the wide range of potential<br />

applications. Thus, on a particular device the software should be easily synthesizable<br />

from a number of existing and custom components. TinyOS addresses<br />

modularity by the concept of components. NesC components expose a welldefined<br />

interface, encapsulating a self-confined piece of software (such a timer<br />

and access to a particular sensor). Each of the pieces can be implemented using<br />

the event-driven model of NesC.<br />

NesC components foster program modularity, as components with different<br />

implementations yet a common interface can be exchanged easily. They also


8.3. <strong>Sensor</strong>-Node <strong>Programming</strong> Frameworks 147<br />

provide a means to structure complex programs or components into a number<br />

of less-complex components. NesC components partition a program in logically<br />

disjunct pieces, each of which is more easily manageable as the entire program.<br />

Each component is active during the entire runtime of the program. NesC components<br />

are a major improvement over basic event-driven systems (such as the<br />

BTnode system software [21]) that only provide the concept of actions to structure<br />

a program.<br />

To address modularity, OSM relies on the concept of program states. OSM<br />

programs can be composed from a number of less-complex states. The composition<br />

is either strictly hierarchical or concurrent. Unlike NesC components, states<br />

in OSM do not only partition the program logically but also in the time domain.<br />

Since programs typically exhibit time-dependent behavior, OSM provides more<br />

flexibility in modeling a program or logical component thus resulting in morestructured<br />

program code.<br />

The implementation of NesC components has one major drawback. NesC<br />

components define the scope <strong>for</strong> variables declared within them, that is, variables<br />

of a particular component cannot be accessed from other components. As<br />

a consequence, the communication among NesC components has to rely on the<br />

passing of (one of the two types of) events with arguments. For per<strong>for</strong>mance<br />

reasons and to limit the number of events passed, programmers often duplicate<br />

variables in several modules. For example, in the EnviroTrack middleware,<br />

group-management messages are sent from a number of components whereas<br />

the functionality to actually send the message is per<strong>for</strong>med in a dedicated component.<br />

This component can process a single message at a time (i.e., there are<br />

no message buffers) and has a boolean variable indicating whether the previous<br />

message has already been sent completely. However, in order to avoid the<br />

overhead of composing a group-management message while the network stack<br />

is not ready to send the next message, the boolean variable is replicated in four<br />

modules. This drawback has been one of the reasons <strong>for</strong> the development of<br />

TinyGALS [29].<br />

Light-weight Execution Environment<br />

A primary goal that TinyOS shares with OSM is the provision of a very lightweight<br />

execution environment. In order to handle concurrency in a small<br />

amount of stack space and to avoid per-thread stacks of multi-threaded systems,<br />

the event-driven model has been chosen. Similar to our approach, NesC<br />

provides asynchronously scheduled actions as a basic programming abstraction.<br />

As in OSM, events are stored in a queue and processed in first-in-first-out order<br />

by invoking the associated action. However, NesC also provides programmers<br />

access to interrupt service routines (ISRs) so that a small amount of processing<br />

associated with hardware events can be per<strong>for</strong>med immediately while long<br />

running tasks are interrupted. Such hardware events can be propagated to a<br />

stack of NesC components. In contrast, interrupts in OSM are hidden from the<br />

programmer. The reason <strong>for</strong> these differences are contrasting assumptions on<br />

the separation of application and execution environment: while in TinyOS programmers<br />

are expected to program their own low-level drivers, <strong>for</strong> example,<br />

<strong>for</strong> sensors and radio transceivers, OSM is based on the assumption that such


148 Chapter 8. Related Work<br />

low-level interfaces are provided by the node’s operating system.<br />

While in OSM actions are never interrupted, ISRs in the NesC can interrupt<br />

actions as well as other ISRs. Actions run to completion only with respect to<br />

other actions. There<strong>for</strong>e, a NesC application may be interrupted anywhere<br />

in an action or ISR. This model can lead to subtle race conditions, which are<br />

typical <strong>for</strong> multi-threaded systems but are not found in typical atomic eventbased<br />

systems. To protect against race conditions, programmers of TinyOS need<br />

to en<strong>for</strong>ce atomic execution of critical sections using the atomic statement.<br />

Though TinyOS does not require per-thread stacks of regular multi-threading<br />

systems, they share some of the undesirable characteristics. As discussed in<br />

Sect. 3.2.4, interruptible (i.e., non-atomic) code is hard to understand and debug<br />

and there<strong>for</strong>e potentially less reliable. Also, there a may be issues with modularity.<br />

8.3.2 Contiki<br />

Contiki [37] has been designed as a light-weight sensor-node operating system.<br />

In order to work efficiently within the constrained resources, the Contiki operating<br />

system is built around an event-driven kernel. Contiki is particularly interesting<br />

as it also supports preemptive multi-threading through loadable modules.<br />

In Contiki, a single program can be built using a combination of the eventdriven<br />

model as well as the multi-threaded model. The authors suggest to build<br />

applications only based on events whenever possible. However, individual,<br />

long-running operations, such as cryptographic operations, can be specified in<br />

separate threads. OSM on the contrary does not support multiple threads and<br />

only allows to specify actions of bounded time.<br />

Building OSM on the Contiki operating system would open up the possibility<br />

to implement activities as proposed by [53]. Activities can be viewed as actions<br />

that are created on the occurrence of an event and which may run concurrently<br />

with the rest of the state machine. They terminate by emitting an event, which<br />

may carry a result as parameter.<br />

8.3.3 Protothreads<br />

Protothreads [38] attack the stack-memory issues of multi-threading from another<br />

perspective. Protothreads provide a multi-threaded model of computation<br />

that do not require individual stacks. There<strong>for</strong>e, Protothreads eliminate what is<br />

probably the dominant reason why sensor-node operating systems are based on<br />

the event-based model. This comes, however, at the cost of local variables. That<br />

is, stackless Protothreads do not support automatically memory-managed local<br />

variables that are commonplace in modern procedural languages. Removing the<br />

per-thread stack also effectively removes the main mechanism to save memory<br />

on temporary variables.<br />

As a consequence, the same issues as in event-driven programming arise: either<br />

data state has to be stored in global variables, which is wasteful on the<br />

memory and has to be manually memory managed. Or dynamic memory management<br />

is required, which has the drawbacks explained in Sect. 3.3 and still<br />

requires significant manual intervention from a programmer. Effectively, the


8.3. <strong>Sensor</strong>-Node <strong>Programming</strong> Frameworks 149<br />

memory issues have been shifted from the operating system to the application<br />

program, which now has to take care of managing temporary variables. As a<br />

consequence, applications written with Protothreads may require more memory<br />

compared to regular threads, leading to a negative grand total.<br />

8.3.4 SenOS<br />

Like OSM, SenOS [75] is a state-based operating system <strong>for</strong> sensor nodes. It<br />

focuses on dynamic reconfigurability of applications programs. Unlike OSM,<br />

programs are specified as flat and sequential state machines, each of which is<br />

represented by a separate state-transition table. As in OSM, actions are associated<br />

with state transitions. All available actions are stored in a static library<br />

that is transferred to the sensor node at the time of programming together with<br />

the operating system kernel. Applications are implemented as state-transition<br />

tables. These tables define all possible machine states and transitions. They also<br />

associate actions from the action library to particular transitions.<br />

SenOS state machines can run concurrently, where each machine runs in its<br />

own thread. Applications programs (as specified by state transition tables) can<br />

be dynamically added to and removed from the system at runtime. However,<br />

the static library of actions cannot be changed unless reprogramming the entire<br />

sensor node.<br />

SenOS relies on task switches <strong>for</strong> concurrency. Its concurrency permits dynamic<br />

scheduling of tasks. In contrast, OSM features a static concurrency model.<br />

The static concurrency model allows very memory efficient implementations<br />

without the need to provide a separate runtime stack <strong>for</strong> each concurrent task.<br />

Furthermore, the static concurrency model makes OSM programs amenable to<br />

<strong>for</strong>mal verification techniques found in state-machine approaches. The flat state<br />

machines approach precludes the use of automatic scoping and lifetime mechanisms<br />

<strong>for</strong> variables, which is a major design point in OSM. It is questionable<br />

if the desired re-configurability and re-programmability can be achieved with<br />

this approach, since programs have to be built exclusively on the preconfigured<br />

actions library without any glue code.


150 Chapter 8. Related Work


9 Conclusions and Future Work<br />

In this final chapter, we conclude by summarizing the contributions of our work<br />

and by discussing its limitations. We also propose future work addressing concrete<br />

limitations of our approach.<br />

9.1 Conclusions<br />

Event-driven programming is a popular paradigm in the domain of sensor networks<br />

in general. For sensor networks that are operating at the very low end<br />

of the resource spectrum it is in fact the predominant programming model. Unlike<br />

the multi-threaded programming model, system support <strong>for</strong> event-driven<br />

programming requires very little of a system’s resources. The event-driven programming<br />

model has been adopted by a large number of programming frameworks<br />

<strong>for</strong> sensor networks, among them TinyOS / NesC, which currently may<br />

be the most popular of all.<br />

Despite its popularity, the event-driven programming model has significant<br />

shortcomings. Particularly in large and complex programs these shortcomings<br />

lead to issues with the readability and structure of the program code, its modularity<br />

and correctness, and, ironically, also the memory efficiency of the developed<br />

programs. Concretely, an event-driven program typically uses more RAM<br />

as a functionally equivalent sequential program because in the event-driven<br />

model a lot of temporary data has to be stored in global variables. A sequential<br />

program would use local variables instead, which are automatically memory<br />

managed and thus their memory is reused.<br />

With respect to these problems, the main contribution of this dissertation is to<br />

show how the event-based model can be extended so that it allows to specify<br />

well-structured, modular, and memory-efficient programs, yet requires as little<br />

runtime support as the original model. We have significantly improved sensornode<br />

programming by extending the event-driven model to a state-based model<br />

with a explicit notion of hierarchical and concurrent program states. We have<br />

also introduced a novel technique to use states as a scoping and lifetime qualifier<br />

<strong>for</strong> variables, so they can be automatically memory managed. This can lead to<br />

significant memory savings in temporary data structures. In the following we<br />

will list our contributions towards our solution in more detail.<br />

9.2 Contributions<br />

Below we will summarize our contributions towards the problem analysis, the<br />

solution approach and its implementation, as well as the evaluation. Some contributions<br />

have also been published in [21, 72, 74].


152 Chapter 9. Conclusions and Future Work<br />

9.2.1 Problem Analysis: Shortcomings of Event-driven<br />

<strong>Programming</strong><br />

In Chapter 4 we have contributed a thorough analysis of the original eventdriven<br />

model in the context of sensor networks. Firstly, we have analyzed<br />

the two main problems of the existing event-driven programming paradigm.<br />

While the basic problem leading to unstructured and un-modular program code,<br />

namely manual state management, has already been identified and documented<br />

in previous work, the memory inefficiency of event-driven sensor-node programs<br />

incurred by manual state management has not been analyzed be<strong>for</strong>e.<br />

Secondly we have contributed by analyzing the anatomy of sensor node applications.<br />

We have found that, though easy and intuitive <strong>for</strong> small and simple<br />

programs, event-driven programs do not describe typical sensor-node applications<br />

very well. That is, the conceptual models that programmers create in their<br />

minds be<strong>for</strong>e implementing a particular program or algorithm does not map<br />

easily to the event-driven programming model. In particular, we have identified<br />

the lack of an abstraction that allows programmers to structure their programs<br />

along the time domain into discrete program phases. We have shown that several<br />

algorithms from the literature are indeed described as systems partitioned<br />

into discrete phases, each having distinctive data state and behavior. The findings<br />

of our problem analysis now allows to better evaluate current and future<br />

programming models <strong>for</strong> sensor networks.<br />

9.2.2 Solution Approach: <strong>State</strong>-based <strong>Programming</strong> with<br />

OSM<br />

To alleviate the problems described above, we have proposed a sensor-node programming<br />

model that is extending the event-driven model with well-known<br />

state-machine abstractions. In Chapter 5 we contribute by presenting such a<br />

model, which we call the OBJECT STATE MODEL (OSM). OSM is based on abstractions<br />

of hierarchically and concurrent state machines. Though they have<br />

been used successfully in embedded-systems programming <strong>for</strong> several years,<br />

they have not yet been applied to the field of sensor networks.<br />

Besides relying on proven programing abstractions, our OSM model also introduces<br />

the concept of state variables. <strong>State</strong> variables supports the memoryefficient<br />

storage and use of temporary data. <strong>State</strong> variables resemble local variables<br />

of sequential programs; both are automatically memory managed and thus<br />

their memory is automatically reused when no longer needed. <strong>State</strong> variables<br />

can help overcome the inherent memory inefficiency of event-driven programming<br />

<strong>for</strong> storing temporary data. Besides that, the maximum memory requirements<br />

of all parts of even highly-concurrent programs can be calculated at compile<br />

time. This allows to check whether a particular program can run on a given<br />

sensor node and to easily identify program parts susceptible <strong>for</strong> optimizations.<br />

Finally, OSM introduces the concept of entry and exit actions (also referred to<br />

as incoming and outgoing actions) to traditional state-machine programming<br />

models. They allow to elegantly and efficiently specify the initialization and<br />

release of state-specific resources, which are common tasks in real-world sensornode<br />

programs.


9.3. Limitations and Future Work 153<br />

9.2.3 Prototypical Implementation<br />

We have implemented a prototypical OSM compiler which generates Clanguage<br />

code, which we have presented in Chapter 6. The compiler translates<br />

state-based OSM programs into event-driven program representations. OSM<br />

programs only require the same minimal runtime support as programs initially<br />

written with event-driven frameworks. Particularly, we show that the code<br />

generated from OSM specifications runs on a only slightly modified version of<br />

our light-weight and event-driven system software <strong>for</strong> the BTnode sensor node.<br />

Though the compilation introduces some memory overhead into the binary, this<br />

overhead can be attributed to the inefficiencies introduced by using an additional<br />

intermediate language (namely Esterel) in the prototypical compiler. The<br />

prototypical implementation of an OSM compiler shows that compiling OSM<br />

programs to efficient event-driven programs is generally feasible.<br />

9.2.4 Evaluation<br />

In Chapter 7 we have shown how automating state and stack management benefits<br />

programmers of real-world sensor-node programs. We have shown the<br />

practical feasibility by presenting an OSM-based re-implementation of part of<br />

EnviroTrack, a system <strong>for</strong> tracking mobile objects with sensor networks, and<br />

other algorithms.<br />

We have re-implemented a significant part of the EnviroTrack sensor-network<br />

middleware, which was originally implemented in the event-driven NesC programming<br />

framework. Using that re-implementation we demonstrate the usefulness<br />

of state-based representations. We have shown that the tedious and<br />

error-prone code <strong>for</strong> state management could be reduced significantly. In the<br />

example, 187 lines of manual state-management code were reduced to an explicit<br />

state representation of only 56 lines, representing a reduction of 70%. The<br />

overall program-size reduction was 31%. An additional 16 lines of the original<br />

EnviroTrack code are used to guard against the unwanted execution of actions in<br />

certain contexts, which is also a <strong>for</strong>m of manual state-management. In OSM, no<br />

extra code is required because the model explicitly specifies which actions can<br />

be called in which context. Finally, we have shown that the OSM code is more<br />

structured. We have shown that logically-related code in OSM is also co-located<br />

in the implementation whereas it is typically scattered throughout much of the<br />

implementation in event-driven code. For example, in OSM there is typically a<br />

single place <strong>for</strong> initializing particular timers, which is also adjacent to the actual<br />

action triggered by the timer. In NesC, however, there are several isolated places<br />

in the code where the timer is initialized, which are also disconnected from the<br />

triggered action.<br />

9.3 Limitations and Future Work<br />

There are a number of limitations and potential improvements with respect to<br />

our approach and methodology, which we will discuss in this section. We also<br />

discuss future work in the broader context of our work.


154 Chapter 9. Conclusions and Future Work<br />

9.3.1 Language and Program Representation<br />

Although the we have shown that the state-based program representation of<br />

OMS can lead to more modular and structured code, there is still much to improve.<br />

The OSM compiler is currently only implemented <strong>for</strong> our textual input<br />

language, though we have also sketched a possible graphical representation<br />

throughout this dissertation. We found that a graphical representation is better<br />

suited to present the big picture of a program (i.e., its coarse-grained structure),<br />

particularly <strong>for</strong> reviewing the design, <strong>for</strong> which the textual representation is less<br />

intuitive. There<strong>for</strong>e we have generally provided both program representations,<br />

the graphical as well as the textual, in examples of this dissertation. On the other<br />

hand, the textual notation is very efficient <strong>for</strong> specifying implementation detail,<br />

such as action names, parameter names, etc. In our experience with graphical<br />

tools <strong>for</strong> UML <strong>State</strong>charts (namely VisualSTATE and Borland Together, both<br />

commercial tools) and <strong>for</strong> SyncCharts (the freely available syncCharts editor<br />

[76]) the specification of implementation details was unsatisfactory. There<strong>for</strong>e<br />

it would be beneficial to support both representations, allowing to freely<br />

switch between and to use the one which is more suitable <strong>for</strong> the task at hand.<br />

New <strong>Model</strong><br />

As in the event-driven programming model, actions in OSM must be nonblocking.<br />

We have found that programmers who had used only sequential programming<br />

languages be<strong>for</strong>e, have difficulties in understanding this. For example,<br />

students programming with our event-driven BTnode system software regularly<br />

used while-loops in one action polling a variable that is set in another<br />

action, wondering why the program would block <strong>for</strong>ever. In general, OSM requires<br />

most programmers to get used to new concepts and to the OSM specification<br />

language in particular. Though this is not a limitation of the programming<br />

model as such, it may hamper its widespread use. OSM does provide semantics<br />

that are compatible with existing event-based systems, however, thus easing the<br />

transition <strong>for</strong> programmers that are already familiar with event-based programming.<br />

Representing Sequential Program Parts<br />

Purely sequential parts of the program flow (i.e., linear sequences of events and<br />

actions without any branches) are tedious to program in OSM because they have<br />

to be explicitly modeled as sequences of states, pairwise connected by a single<br />

transition (see the BUSY state of Prog. 7.4 on page 133 as an example). In such<br />

cases wait-operations, as typically provided by multi-threaded programming<br />

models, might be more natural. To solve this issue, it may be worth investigating<br />

how Protothreads [38] could be used to implement sequential program parts.<br />

Representing Composite Events<br />

Likewise, reactions to composite events (i.e., meta-events made up of multiple<br />

events, <strong>for</strong> example, “events e1, e2, and e3 in any order”) cannot be specified


9.3. Limitations and Future Work 155<br />

concisely. Instead, all intermediate states and transitions have to be modeled<br />

explicitly even though only the reaction to the event completing the composite<br />

event may be relevant. Introducing more elaborate triggers of transitions and a<br />

concise notation could solve this problem. Such a trigger mechanism could be<br />

implemented as semantic sugar using the existing concepts of states and transitions<br />

but hiding them from the user.<br />

9.3.2 Real-Time Aspects of OSM<br />

OSM does not address real-time behavior. It does not provide any mechanisms<br />

to specify deadlines and to determine how much time transitions and their associated<br />

actions take. There is, however, a large body of work on state-machine<br />

based real-time systems. Future work could investigate how these approaches<br />

can be integrated into OSM.<br />

Also, if actions generate events, the order of events in the queue (and hence<br />

the system behavior) may depend on the execution speed of actions.<br />

9.3.3 Memory Issues<br />

Queue Size and Runtime-stack Size Estimations<br />

One of the reasons why OSM has been developed is to resolve the memory issues<br />

of temporary-data management, which is prevalent in event-driven programming.<br />

While OSM can calculate the program’s memory usage at compile<br />

time, the maximum queue-size (required <strong>for</strong> storing events that cannot be processed<br />

immediately) remains unknown. As a consequence, the event queue may<br />

be oversized, thus wasting memory, or undersized, resulting in lost data at best<br />

but more likely in a program crash. Future work could estimate the maximum<br />

queue size required.<br />

The other unknown memory factor besides the queue size is the system’s runtime<br />

stack-size. The runtime stack is used <strong>for</strong> holding in<strong>for</strong>mation of function<br />

calls (i.e., its parameters and the return address) as well as the local variables<br />

of OSM actions and regular functions of the host language. (<strong>State</strong> variables<br />

are stored statically and do not use the stack.) Being able to precisely estimate<br />

the stack size would be beneficial <strong>for</strong> the reasons described above. There has<br />

been some work on estimating stack sizes, particularly in the context of multithreaded<br />

programs. In future work the OSM compiler could be extended to<br />

per<strong>for</strong>m such an estimate.<br />

Applicability of <strong>State</strong> Variables<br />

<strong>State</strong> variables can be tied to entire state hierarchies only. However, a more<br />

flexible scoping mechanism of state variables may be desirable. Consider the<br />

state hierarchy depicted below where a variable v1 is being used in the states A<br />

and C but not in D. In OSM such a variable would be modeled as a state variable<br />

of root R. Its scope and lifetime would extend to all substates of R including<br />

D. In the given case, OSM is not able to reuse the memory holding v1 in D,<br />

<strong>for</strong> example, <strong>for</strong> D’s own private variable v2. Flexible scoping mechanism that<br />

allows to include selected states only (as opposed to being bound to the state


156 Chapter 9. Conclusions and Future Work<br />

hierarchy) could further increase the memory efficiency but are left <strong>for</strong> future<br />

work.<br />

v 1<br />

Root<br />

9.4 Concluding Remarks<br />

A<br />

C<br />

v 1<br />

There are still features and tools missing to make OSM an easy and intuitive to<br />

use programming framework <strong>for</strong> resource constrained sensor nodes. Particularly<br />

more experience is required to evaluate the value of OSM <strong>for</strong> large projects<br />

and to determine what needs to be included in future versions. However, we believe<br />

that OSM is a big step towards extending the application domain of eventbased<br />

programming to larger and more complex systems.<br />

B<br />

D<br />

v<br />

2


A OSM Code Generation<br />

This Appendix demonstrates the stages of the code generation process employed<br />

by our OSM compiler prototype <strong>for</strong> a small, yet complex OSM program<br />

example. In Sect. A.1 we present this program, both in graphical as well as<br />

in textual representation. Sect. A.2 presents the C-language include file that is<br />

generated from the OSM example program. It includes the mapping of the OSM<br />

program’s variables to a C-language struct as well as the prototypes of all actions<br />

defined in the OSM program. Sect. A.3 present the control flow mapping of the<br />

OSM program to the Esterel language. The generated Esterel code is translated<br />

to the C language by an Esterel compiler. The compiler’s output is presented<br />

in Sect. A.4. A functionally equivalent version optimized <strong>for</strong> memory efficiency<br />

is also presented. Finally in Sections A.5.1 and A.5.2 we present the Makefile<br />

<strong>for</strong> compiling the OSM example program and the output of an OSM compiler<br />

during a compilation run.<br />

A.1 OSM Example<br />

PROG<br />

PROG_1<br />

inA()<br />

A<br />

outA()<br />

e<br />

int a<br />

B<br />

int b<br />

C<br />

int c<br />

f<br />

outA()<br />

outB()<br />

f e<br />

inC1() inC2()<br />

PROG_2<br />

D<br />

int a<br />

E<br />

outE()<br />

int b<br />

prE()<br />

f<br />

F<br />

int a<br />

int c<br />

inE(a,b)<br />

f<br />

outF(a,c)<br />

outD()<br />

Figure A.1: Graphical representation of test.osm, a program <strong>for</strong> testing the<br />

OSM compiler.<br />

e<br />

G<br />

e


158 Appendix A. OSM Code Generation<br />

1 // -------------------------------------------------------<br />

2 // test.osm -- A program <strong>for</strong> testing the OSM compiler<br />

3 // -------------------------------------------------------<br />

4<br />

5 state PROG {<br />

6 state PROG_1 {<br />

7<br />

8 initial state A {<br />

9 var int a;<br />

10 onEntry: start/ inA();<br />

11 e / outA() -> B;<br />

12 f / outA() -> A;<br />

13 } // end state A<br />

14<br />

15 state B {<br />

16 var int b;<br />

17 f / outB() -> C;<br />

18 e / -> C;<br />

19 } // end state B<br />

20<br />

21 state C {<br />

22 var int c;<br />

23 onEntry: B -> f/ inC1();<br />

24 onEntry: B -> e/ inC2();<br />

25 } // end state C<br />

26<br />

27 } // end PROG_1<br />

28 ||<br />

29 state PROG_2 {<br />

30 initial state D {<br />

31 var int a;<br />

32<br />

33 initial state E {<br />

34 var int b;<br />

35 onEntry: f / inE(a, b);<br />

36 f / outE() -> F;<br />

37 // onPreemption: prE();<br />

38 } // end state E<br />

39 state F {<br />

40 var int a;<br />

41 var int c;<br />

42 f / outF(a, c) -> E;<br />

43 } // end state F<br />

44<br />

45 e / outD() -> G;<br />

46 } // end state D<br />

47<br />

48 state G { e / -> D; }<br />

49 } // end PROG_2<br />

50 } // end PROG


A.2. Variable Mapping (C include file) 159<br />

A.2 Variable Mapping (C include file)<br />

1 /* This include file was generated by OSM on<br />

2 * Samstag, Juni 24, 2006 at 09:31:50.<br />

3 * OSM compiler written by Oliver Kasten */<br />

4<br />

5<br />

6 /* prevent the esterel compiler from declaring functions extern */<br />

7 #define _NO_EXTERN_DEFINITIONS<br />

8<br />

9 // function <strong>for</strong> making a discrete step in the state automaton<br />

10 int STATE_PROG();<br />

11 // function <strong>for</strong> resetting all inputs of the state automaton<br />

12 int STATE_PROG_reset();<br />

13<br />

14 /* no outputs events from state PROG */<br />

15<br />

16 /* exit actions prototypes */<br />

17 void outA();<br />

18 void outB();<br />

19 void outD();<br />

20 void outE();<br />

21 void outF(int*, int*);<br />

22 #define outF_PROG_PROG_2_D_F_a_PROG_PROG_2_D_F_c() outF( \<br />

23 &_statePROG._par_PROG_1_PROG_2._statePROG_2._seq_D_G._stateD._seq_E_F._stateF.a, \<br />

24 &_statePROG._par_PROG_1_PROG_2._statePROG_2._seq_D_G._stateD._seq_E_F._stateF.c )<br />

25<br />

26 /* entry actions prototypes */<br />

27 void inA();<br />

28 void inC1();<br />

29 void inC2();<br />

30 void inE( int*, int*);<br />

31 #define inE_PROG_PROG_2_D_a_PROG_PROG_2_D_E_b() inE( \<br />

32 &_statePROG._par_PROG_1_PROG_2._statePROG_2._seq_D_G._stateD.a, \<br />

33 &_statePROG._par_PROG_1_PROG_2._statePROG_2._seq_D_G._stateD._seq_E_F._stateE.b )<br />

34<br />

35 /* preemption actions */<br />

36 void prE();<br />

37<br />

38 struct statePROG {<br />

39 /* parallel machine */<br />

40 union par_PROG_1_PROG_2 {<br />

41 /* contained states */<br />

42 struct statePROG_1 {<br />

43 /* sequential machine */<br />

44 union seq_A_B_C {<br />

45 /* contained states */<br />

46 struct stateA {<br />

47 int a;<br />

48 } _stateA;<br />

49 struct stateB {<br />

50 int b;<br />

51 } _stateB;<br />

52 struct stateC {<br />

53 int a;<br />

54 } _stateC;<br />

55 } _seq_A_B_C;<br />

56 } _statePROG_1;<br />

57 struct statePROG_2 {<br />

58 /* sequential machine */<br />

59 union seq_D_G {<br />

60 /* contained states */<br />

61 struct stateD {<br />

62 int a;<br />

63 /* sequential machine */<br />

64 union seq_E_F {<br />

65 /* contained states */<br />

66 struct stateE {<br />

67 int b;<br />

68 } _stateE;<br />

69 struct stateF {<br />

70 int a;<br />

71 int c;


160 Appendix A. OSM Code Generation<br />

72 } _stateF;<br />

73 } _seq_E_F;<br />

74 } _stateD;<br />

75 struct stateG {<br />

76 } _stateG;<br />

77 } _seq_D_G;<br />

78 } _statePROG_2;<br />

79 } _par_PROG_1_PROG_2;<br />

80 } _statePROG;


A.3. Control Flow Mapping–Stage One (Esterel) 161<br />

40 signal gamma1, gamma2, gamma3 in<br />

41 % init:<br />

42 emit gamma1; % start initial state<br />

43 % mainloop<br />

44 loop<br />

45 trap L in<br />

46 % STATE_A<br />

47 present gamma1 then<br />

48 signal alpha in<br />

49 emit alpha;<br />

50 run STATE_A [signal gamma1/ xi1, gamma2/ xi2];<br />

51 present alpha else exit L end present<br />

52 end signal<br />

53 end present;<br />

54 % STATE_B<br />

55 present gamma2 then<br />

56 signal alpha in<br />

57 emit alpha;<br />

58 run STATE_B [signal gamma3/ xi1, gamma3/ xi2];<br />

59 present alpha else exit L end present<br />

60 end signal<br />

61 end present;<br />

62 % STATE_C<br />

63 present gamma3 then<br />

64 signal alpha in<br />

65 emit alpha;<br />

66 run STATE_C;<br />

67 present alpha else exit L end present<br />

68 end signal<br />

69 end present;<br />

70 halt % never reached<br />

71 end trap<br />

72 end loop<br />

73 end signal<br />

74 end module % CONSTELLATION_A_B_C<br />

75<br />

76 module STATE_A:<br />

77 input e, f, start_PROG_1;<br />

78 output xi1, xi2;<br />

79 procedure inA()(), outA()();<br />

80 % onEntry actions<br />

81 present<br />

82 case start_PROG_1 do call inA()();<br />

83 end present;<br />

A.3 Control Flow Mapping–Stage One<br />

(Esterel)<br />

1 % This Esterel code was generated by OSM on<br />

2 % Samstag, Juni 24, 2006 at 06:41:44.<br />

3 % OSM compiler written by Oliver Kasten <br />

4<br />

5 % o-star<br />

6 module STATE_PROG:<br />

7 input e, f;<br />

8 % no outputs<br />

9 % no function declarations<br />

10 run MACRO_PROG_1_PROG_2; % parallel substates<br />

11 halt<br />

12 end module % STATE_PROG<br />

13<br />

14 module MACRO_PROG_1_PROG_2:<br />

15 input e, f;<br />

16 % no output<br />

17 trap T in<br />

18 run STATE_PROG_1<br />

19 ||<br />

20 run STATE_PROG_2<br />

21 end trap<br />

22 end module % MACRO_PROG_1_PROG_2<br />

23<br />

24 % o-star<br />

25 module STATE_PROG_1:<br />

26 input e, f;<br />

27 % no outputs<br />

28 % no function declarations<br />

29 signal start_PROG_1 in<br />

30 emit start_PROG_1; % emit pseudo event start <strong>for</strong> superstate PROG_1<br />

31 run CONSTELLATION_A_B_C; % sequential substates<br />

32 end signal;<br />

33 halt<br />

34 end module % STATE_PROG_1<br />

35<br />

36 module CONSTELLATION_A_B_C:<br />

37 input e, f, start_PROG_1;<br />

38 % no output<br />

39


162 Appendix A. OSM Code Generation<br />

128 module STATE_C:<br />

129 input e, f;<br />

130 % no outputs<br />

131 procedure inC1()(), inC2()();<br />

132 present<br />

133 case f do call inC1()();<br />

134 case e do call inC2()();<br />

135 end present;<br />

136 halt<br />

137 end module % STATE_C<br />

138<br />

139 % o-star<br />

140 module STATE_PROG_2:<br />

141 input e, f;<br />

142 % no outputs<br />

143 % no function declarations<br />

144 run CONSTELLATION_D_G; % sequential substates<br />

145 halt<br />

84 trap T in<br />

85 signal alpha, iota, omega in<br />

86 emit alpha;<br />

87 suspend<br />

88 % suspend<br />

89 halt<br />

90 % when <br />

91 when immediate iota<br />

92 ||<br />

93 every tick do<br />

94 present<br />

95 case f do emit iota; emit xi1; call outA()(); exit T<br />

96 case e do emit iota; emit xi2; call outA()(); exit T<br />

97 end present<br />

98 end every<br />

99 end signal<br />

100 end trap<br />

101 end module % STATE_A<br />

102<br />

103 module STATE_B:<br />

104 input e, f;<br />

105 output xi1, xi2;<br />

106 procedure outB()();<br />

107 % no onEntry actions<br />

108 trap T in<br />

109 signal alpha, iota, omega in<br />

110 emit alpha;<br />

111 suspend<br />

112 % suspend<br />

113 halt<br />

114 % when <br />

115 when immediate iota<br />

116 ||<br />

117 every tick do<br />

118 present<br />

119 case f do emit iota; emit xi1; call outB()(); exit T<br />

120 case e do emit iota; emit xi2; exit T<br />

146 end module % STATE_PROG_2<br />

147<br />

148 module CONSTELLATION_D_G:<br />

149 input e, f;<br />

150 % no output<br />

151<br />

152 signal gamma1, gamma2 in<br />

153 % init:<br />

154 emit gamma1; % start initial state<br />

155 % mainloop<br />

156 loop<br />

157 trap L in<br />

158 % STATE_D<br />

159 present gamma1 then<br />

160 signal alpha in<br />

161 emit alpha;<br />

162 run STATE_D [signal gamma2/ xi1];<br />

163 present alpha else exit L end present<br />

164 end signal<br />

165 end present;<br />

166 % STATE_G<br />

167 present gamma2 then<br />

168 signal alpha in<br />

169 emit alpha;<br />

170 run STATE_G [signal gamma1/ xi1];<br />

171 present alpha else exit L end present<br />

121 end present<br />

122 end every<br />

123 end signal<br />

124 end trap<br />

125 end module % STATE_B<br />

126<br />

127 % o-star


A.3. Control Flow Mapping–Stage One (Esterel) 163<br />

216 signal alpha in<br />

217 emit alpha;<br />

218 run STATE_E [signal gamma2/ xi1];<br />

219 present alpha else exit L end present<br />

220 end signal<br />

221 end present;<br />

222 % STATE_F<br />

223 present gamma2 then<br />

224 signal alpha in<br />

225 emit alpha;<br />

226 run STATE_F [signal gamma1/ xi1];<br />

227 present alpha else exit L end present<br />

228 end signal<br />

229 end present;<br />

230 halt % never reached<br />

231 end trap<br />

232 end loop<br />

233 end signal<br />

234 end module % CONSTELLATION_E_F<br />

235<br />

236 module STATE_E:<br />

237 input f;<br />

238 output xi1;<br />

239 procedure inE_PROG_PROG_2_D_a_PROG_PROG_2_D_E_b()(), outE()();<br />

240 % onEntry actions<br />

241 present<br />

242 case f do call inE_PROG_PROG_2_D_a_PROG_PROG_2_D_E_b()();<br />

243 end present;<br />

244 trap T in<br />

245 signal alpha, iota, omega in<br />

246 emit alpha;<br />

247 suspend<br />

248 % suspend<br />

249 halt<br />

250 % when <br />

251 when immediate iota<br />

252 ||<br />

253 every tick do<br />

254 present<br />

255 case f do emit iota; emit xi1; call outE()(); exit T<br />

172 end signal<br />

173 end present;<br />

174 halt % never reached<br />

175 end trap<br />

176 end loop<br />

177 end signal<br />

178 end module % CONSTELLATION_D_G<br />

179<br />

180 module STATE_D:<br />

181 input e, f;<br />

182 output xi1;<br />

183 procedure prE()(), outD()();<br />

184 % no onEntry actions<br />

185 trap T in<br />

186 signal alpha, iota, omega in<br />

187 emit alpha;<br />

188 suspend<br />

189 % suspend<br />

190 run CONSTELLATION_E_F; % sequential substates<br />

191 halt<br />

192 % when <br />

193 when immediate iota<br />

194 ||<br />

195 every tick do<br />

196 present<br />

197 case e do emit iota; emit xi1; call prE()(); call outD()(); exit T<br />

198 end present<br />

199 end every<br />

200 end signal<br />

201 end trap<br />

202 end module % STATE_D<br />

203<br />

204 module CONSTELLATION_E_F:<br />

205 input f;<br />

206 % no output<br />

207<br />

208 signal gamma1, gamma2 in<br />

209 % init:<br />

210 emit gamma1; % start initial state<br />

256 end present<br />

257 end every<br />

258 end signal<br />

259 end trap<br />

211 % mainloop<br />

212 loop<br />

213 trap L in<br />

214 % STATE_E<br />

215 present gamma1 then


164 Appendix A. OSM Code Generation<br />

304 end every<br />

305 end signal<br />

306 end trap<br />

307 end module % STATE_G<br />

A.4 Control Flow Mapping–Stage Two (C)<br />

A.4.1 Output of the Esterel Compiler<br />

1 /* sscc : C CODE OF SORTED EQUATIONS STATE_PROG - INLINE MODE */<br />

2<br />

3 /* AUXILIARY DECLARATIONS */<br />

4<br />

5 #ifndef STRLEN<br />

6 #define STRLEN 81<br />

7 #endif<br />

8 #define _COND(A,B,C) ((A)?(B):(C))<br />

9 #ifdef TRACE_ACTION<br />

10 #include <br />

11 #endif<br />

12 #ifndef NULL<br />

13 #define NULL ((char*)0)<br />

260 end module % STATE_E<br />

261<br />

262 module STATE_F:<br />

263 input f;<br />

264 output xi1;<br />

265 procedure outF_PROG_PROG_2_D_F_a_PROG_PROG_2_D_F_c()();<br />

266 % no onEntry actions<br />

267 trap T in<br />

268 signal alpha, iota, omega in<br />

269 emit alpha;<br />

270 suspend<br />

271 % suspend<br />

272 halt<br />

273 % when <br />

274 when immediate iota<br />

275 ||<br />

276 every tick do<br />

277 present<br />

278 case f do emit iota; emit xi1;<br />

279 call outF_PROG_PROG_2_D_F_a_PROG_PROG_2_D_F_c()(); exit T<br />

280 end present<br />

281 end every<br />

282 end signal<br />

283 end trap<br />

284 end module % STATE_F<br />

285<br />

286 module STATE_G:<br />

287 input e;<br />

288 output xi1;<br />

289 % no function declarations<br />

290 % no onEntry actions<br />

291 trap T in<br />

292 signal alpha, iota, omega in<br />

14 #endif<br />

[...]<br />

48 #include "test.h"<br />

49<br />

50 /* EXTERN DECLARATIONS */<br />

51<br />

52 #ifndef _NO_EXTERN_DEFINITIONS<br />

53 #ifndef _NO_PROCEDURE_DEFINITIONS<br />

54 #ifndef _inA_DEFINED<br />

55 #ifndef inA<br />

56 extern void inA();<br />

57 #endif<br />

58 #endif<br />

59 #ifndef _outA_DEFINED<br />

60 #ifndef outA<br />

61 extern void outA();<br />

62 #endif<br />

293 emit alpha;<br />

294 suspend<br />

295 % suspend<br />

296 halt<br />

297 % when <br />

298 when immediate iota<br />

299 ||<br />

300 every tick do<br />

301 present<br />

302 case e do emit iota; emit xi1; exit T<br />

303 end present


A.4. Control Flow Mapping–Stage Two (C) 165<br />

107 /* MEMORY ALLOCATION */<br />

108<br />

109 static boolean __STATE_PROG_V0;<br />

110 static boolean __STATE_PROG_V1;<br />

111<br />

112 /* INPUT FUNCTIONS */<br />

113<br />

114 void STATE_PROG_I_e () {<br />

115 __STATE_PROG_V0 = _true;<br />

116 }<br />

117 void STATE_PROG_I_f () {<br />

118 __STATE_PROG_V1 = _true;<br />

119 }<br />

120<br />

121 /* PRESENT SIGNAL TESTS */<br />

122<br />

123 #define __STATE_PROG_A1 \<br />

124 __STATE_PROG_V0<br />

125 #define __STATE_PROG_A2 \<br />

126 __STATE_PROG_V1<br />

63 #endif<br />

64 #ifndef _outB_DEFINED<br />

65 #ifndef outB<br />

66 extern void outB();<br />

67 #endif<br />

68 #endif<br />

69 #ifndef _inC1_DEFINED<br />

70 #ifndef inC1<br />

71 extern void inC1();<br />

72 #endif<br />

73 #endif<br />

74 #ifndef _inC2_DEFINED<br />

75 #ifndef inC2<br />

76 extern void inC2();<br />

77 #endif<br />

78 #endif<br />

79 #ifndef _prE_DEFINED<br />

80 #ifndef prE<br />

81 extern void prE();<br />

82 #endif<br />

83 #endif<br />

84 #ifndef _outD_DEFINED<br />

85 #ifndef outD<br />

86 extern void outD();<br />

87 #endif<br />

88 #endif<br />

89 #ifndef _inE_PROG_PROG_2_D_a_PROG_PROG_2_D_E_b_DEFINED<br />

90 #ifndef inE_PROG_PROG_2_D_a_PROG_PROG_2_D_E_b<br />

91 extern void inE_PROG_PROG_2_D_a_PROG_PROG_2_D_E_b();<br />

92 #endif<br />

93 #endif<br />

94 #ifndef _outE_DEFINED<br />

95 #ifndef outE<br />

96 extern void outE();<br />

97 #endif<br />

98 #endif<br />

99 #ifndef _outF_PROG_PROG_2_D_F_a_PROG_PROG_2_D_F_c_DEFINED<br />

100 #ifndef outF_PROG_PROG_2_D_F_a_PROG_PROG_2_D_F_c<br />

101 extern void outF_PROG_PROG_2_D_F_a_PROG_PROG_2_D_F_c();<br />

127<br />

128 /* ASSIGNMENTS */<br />

129<br />

130 #define __STATE_PROG_A3 \<br />

131 __STATE_PROG_V0 = _false<br />

132 #define __STATE_PROG_A4 \<br />

133 __STATE_PROG_V1 = _false<br />

134<br />

135 /* PROCEDURE CALLS */<br />

136<br />

137 #define __STATE_PROG_A5 \<br />

138 inA()<br />

139 #define __STATE_PROG_A6 \<br />

140 outA()<br />

141 #define __STATE_PROG_A7 \<br />

142 outA()<br />

143 #define __STATE_PROG_A8 \<br />

144 outB()<br />

145 #define __STATE_PROG_A9 \<br />

146 inC1()<br />

147 #define __STATE_PROG_A10 \<br />

148 inC2()<br />

149 #define __STATE_PROG_A11 \<br />

150 prE()<br />

102 #endif<br />

103 #endif<br />

104 #endif<br />

105 #endif<br />

106


166 Appendix A. OSM Code Generation<br />

195 E[2] = __STATE_PROG_R[4]&&!(__STATE_PROG_R[0]);<br />

196 E[3] = E[2]&&(<br />

197 __STATE_PROG_A2);<br />

198 E[4] = E[3]||__STATE_PROG_R[0];<br />

199 E[5] = E[4];<br />

200 E[6] = __STATE_PROG_R[21]&&!(__STATE_PROG_R[0]);<br />

201 E[7] = E[6]&&(<br />

202 __STATE_PROG_A1);<br />

203 E[8] = E[7];<br />

204 E[9] = E[7]||__STATE_PROG_R[0];<br />

205 E[10] = E[9];<br />

206 E[11] = __STATE_PROG_R[11]&&!(__STATE_PROG_R[0]);<br />

207 E[12] = (<br />

208 __STATE_PROG_A1)&&E[11];<br />

209 if (E[12]) {<br />

210 __STATE_PROG_A11;<br />

211 }<br />

212 if (E[12]) {<br />

213 __STATE_PROG_A12;<br />

214 }<br />

215 E[13] = E[12];<br />

216 E[14] = __STATE_PROG_R[17]||__STATE_PROG_R[16];<br />

217 E[15] = __STATE_PROG_R[18]||E[14];<br />

218 E[16] = __STATE_PROG_R[14]||__STATE_PROG_R[13];<br />

219 E[17] = __STATE_PROG_R[15]||E[16];<br />

220 E[18] = __STATE_PROG_R[12]||E[15]||E[17];<br />

221 E[19] = E[18]&&!(__STATE_PROG_R[0]);<br />

222 E[20] = E[19]&&!(E[12]);<br />

223 E[21] = E[20]&&__STATE_PROG_R[18];<br />

224 E[22] = E[21]&&(<br />

225 __STATE_PROG_A2);<br />

226 if (E[22]) {<br />

227 __STATE_PROG_A15;<br />

228 }<br />

229 E[23] = E[22];<br />

230 E[24] = E[20]&&__STATE_PROG_R[15];<br />

231 E[25] = (<br />

232 __STATE_PROG_A2)&&E[24];<br />

233 if (E[25]) {<br />

234 __STATE_PROG_A14;<br />

235 }<br />

236 E[26] = E[25];<br />

237 E[27] = E[12];<br />

238 E[28] = __STATE_PROG_R[7]&&!(__STATE_PROG_R[0]);<br />

151 #define __STATE_PROG_A12 \<br />

152 outD()<br />

153 #define __STATE_PROG_A13 \<br />

154 inE_PROG_PROG_2_D_a_PROG_PROG_2_D_E_b()<br />

155 #define __STATE_PROG_A14 \<br />

156 outE()<br />

157 #define __STATE_PROG_A15 \<br />

158 outF_PROG_PROG_2_D_F_a_PROG_PROG_2_D_F_c()<br />

159<br />

160 /* FUNCTIONS RETURNING NUMBER OF EXEC */<br />

161<br />

162 int STATE_PROG_number_of_execs () {<br />

163 return (0);<br />

164 }<br />

165<br />

166 /* AUTOMATON (STATE ACTION-TREES) */<br />

167<br />

168 static void __STATE_PROG__reset_input () {<br />

169 __STATE_PROG_V0 = _false;<br />

170 __STATE_PROG_V1 = _false;<br />

171 }<br />

172<br />

173 /* REDEFINABLE BIT TYPE */<br />

174<br />

175 #ifndef __SSC_BIT_TYPE_DEFINED<br />

176 typedef char __SSC_BIT_TYPE;<br />

177 #endif<br />

178<br />

179 /* REGISTER VARIABLES */<br />

180<br />

181 static __SSC_BIT_TYPE __STATE_PROG_R[22] = {_true,<br />

182 _false, _false, _false, _false, _false, _false, _false,<br />

183 _false, _false, _false, _false, _false, _false, _false,<br />

184 _false, _false, _false, _false, _false, _false, _false };<br />

185<br />

186 /* AUTOMATON ENGINE */<br />

187<br />

188 int STATE_PROG () {<br />

189 /* AUXILIARY VARIABLES */<br />

190<br />

191 static __SSC_BIT_TYPE E[106];<br />

192 E[0] = (<br />

193 __STATE_PROG_A2);<br />

194 E[1] = __STATE_PROG_R[0];


A.4. Control Flow Mapping–Stage Two (C) 167<br />

283 E[17] = (E[17]&&!(__STATE_PROG_R[15]))||E[24];<br />

284 E[46] = (E[17]||E[25])&&E[16]&&E[25];<br />

285 E[49] = E[20]&&__STATE_PROG_R[17];<br />

286 E[50] = (E[19]&&__STATE_PROG_R[17])||(E[49]&&E[22]&&__STATE_PROG_R[17]);<br />

287 E[51] = E[20]&&__STATE_PROG_R[16];<br />

288 E[51] = (E[49]&&!(E[22]))||(E[51]&&!(E[22])&&__STATE_PROG_R[16])||<br />

289 (((E[51]&&E[22])||E[19])&&__STATE_PROG_R[16]);<br />

290 E[14] = (E[15]&&!(E[14]))||E[50]||E[51];<br />

291 E[21] = E[21]&&!((<br />

292 __STATE_PROG_A2));<br />

293 E[21] = (E[19]&&__STATE_PROG_R[18])||E[21];<br />

294 E[15] = (E[15]&&!(__STATE_PROG_R[18]))||E[21];<br />

295 E[49] = (E[15]||E[22])&&E[14]&&E[22];<br />

296 E[52] = E[49]||E[46];<br />

297 E[53] = E[52];<br />

298 E[54] = E[25];<br />

299 E[55] = _false;<br />

300 E[56] = E[12];<br />

301 E[57] = __STATE_PROG_R[10]&&!(__STATE_PROG_R[0]);<br />

302 E[58] = E[57]&&!(E[12]);<br />

303 E[59] = E[58]||E[22];<br />

304 E[52] = E[58]||E[52];<br />

305 E[58] = E[59]&&E[52];<br />

306 E[60] = E[58]&&(<br />

307 __STATE_PROG_A2);<br />

308 if (E[60]) {<br />

309 __STATE_PROG_A13;<br />

310 }<br />

311 E[6] = E[6]&&!((<br />

312 __STATE_PROG_A1));<br />

313 E[61] = __STATE_PROG_R[19]&&!(__STATE_PROG_R[0]);<br />

314 E[62] = __STATE_PROG_R[20]&&!(__STATE_PROG_R[0]);<br />

315 E[61] = (E[62]&&!(E[7]))||(E[61]&&!(E[7])&&__STATE_PROG_R[19])||<br />

316 (E[61]&&E[7]&&__STATE_PROG_R[19]);<br />

317 E[62] = E[62]&&E[7]&&__STATE_PROG_R[20];<br />

318 E[63] = __STATE_PROG_R[20]||__STATE_PROG_R[19];<br />

319 E[64] = E[63]||__STATE_PROG_R[21];<br />

320 E[63] = (E[64]&&!(E[63]))||E[62]||E[61];<br />

321 E[65] = (E[64]&&!(__STATE_PROG_R[21]))||E[6];<br />

322 E[7] = (E[65]||E[7])&&E[63]&&E[7];<br />

323 E[66] = E[58]&&!((<br />

324 __STATE_PROG_A2));<br />

325 E[66] = E[66]||E[60];<br />

326 E[52] = !(E[59])&&E[52];<br />

239 E[29] = E[28]&&(<br />

240 __STATE_PROG_A2);<br />

241 if (E[29]) {<br />

242 __STATE_PROG_A8;<br />

243 }<br />

244 E[28] = E[28]&&!((<br />

245 __STATE_PROG_A2));<br />

246 E[30] = E[28]&&(<br />

247 __STATE_PROG_A1);<br />

248 E[31] = E[29]||E[30];<br />

249 E[32] = E[31];<br />

250 E[33] = E[31];<br />

251 if (E[3]) {<br />

252 __STATE_PROG_A6;<br />

253 }<br />

254 E[2] = E[2]&&!((<br />

255 __STATE_PROG_A2));<br />

256 E[34] = E[2]&&(<br />

257 __STATE_PROG_A1);<br />

258 if (E[34]) {<br />

259 __STATE_PROG_A7;<br />

260 }<br />

261 E[35] = E[3]||E[34];<br />

262 E[36] = E[35];<br />

263 E[37] = E[34];<br />

264 E[38] = (<br />

265 __STATE_PROG_A1);<br />

266 E[39] = _false;<br />

267 E[40] = _false;<br />

268 E[41] = E[7];<br />

269 E[42] = _false;<br />

270 E[43] = E[22];<br />

271 E[44] = _false;<br />

272 E[45] = E[25];<br />

273 E[46] = E[20]&&__STATE_PROG_R[14];<br />

274 E[19] = E[19]&&E[12];<br />

275 E[47] = (E[19]&&__STATE_PROG_R[14])||(E[46]&&E[25]&&__STATE_PROG_R[14]);<br />

276 E[48] = E[20]&&__STATE_PROG_R[13];<br />

277 E[48] = (E[46]&&!(E[25]))||(E[48]&&!(E[25])&&__STATE_PROG_R[13])||<br />

278 ((E[19]||(E[48]&&E[25]))&&__STATE_PROG_R[13]);<br />

279 E[16] = (E[17]&&!(E[16]))||E[47]||E[48];<br />

280 E[24] = !((<br />

281 __STATE_PROG_A2))&&E[24];<br />

282 E[24] = (E[19]&&__STATE_PROG_R[15])||E[24];


168 Appendix A. OSM Code Generation<br />

371 E[82] = _false;<br />

372 E[83] = E[35];<br />

373 E[28] = E[28]&&!((<br />

374 __STATE_PROG_A1));<br />

375 E[84] = __STATE_PROG_R[5]&&!(__STATE_PROG_R[0]);<br />

376 E[85] = __STATE_PROG_R[6]&&!(__STATE_PROG_R[0]);<br />

377 E[84] = (!(E[31])&&E[85])||(!(E[31])&&E[84]&&__STATE_PROG_R[5])||<br />

378 (E[31]&&E[84]&&__STATE_PROG_R[5]);<br />

379 E[85] = E[31]&&E[85]&&__STATE_PROG_R[6];<br />

380 E[86] = __STATE_PROG_R[6]||__STATE_PROG_R[5];<br />

381 E[87] = E[86]||__STATE_PROG_R[7];<br />

382 E[86] = (E[87]&&!(E[86]))||E[85]||E[84];<br />

383 E[88] = (E[87]&&!(__STATE_PROG_R[7]))||E[28];<br />

384 E[30] = E[31]&&(E[88]||E[29]||E[30])&&E[86];<br />

385 E[89] = __STATE_PROG_R[2]&&!(__STATE_PROG_R[0]);<br />

386 E[90] = __STATE_PROG_R[3]&&!(__STATE_PROG_R[0]);<br />

387 E[89] = (!(E[35])&&E[90])||(!(E[35])&&E[89]&&__STATE_PROG_R[2])||<br />

388 (E[35]&&E[89]&&__STATE_PROG_R[2]);<br />

389 E[90] = E[35]&&E[90]&&__STATE_PROG_R[3];<br />

390 E[91] = __STATE_PROG_R[3]||__STATE_PROG_R[2];<br />

391 E[92] = E[91]||__STATE_PROG_R[4];<br />

392 E[91] = (E[92]&&!(E[91]))||E[90]||E[89];<br />

393 E[2] = E[2]&&!((<br />

394 __STATE_PROG_A1));<br />

395 E[93] = (E[92]&&!(__STATE_PROG_R[4]))||E[2];<br />

396 E[35] = E[35]&&(E[93]||E[3]||E[34])&&E[91];<br />

397 E[94] = E[30]||E[35];<br />

398 E[95] = E[94]||__STATE_PROG_R[0];<br />

399 E[96] = !(E[4])&&E[95];<br />

400 E[97] = E[96]&&E[34];<br />

401 E[98] = E[97];<br />

402 E[95] = E[4]&&E[95];<br />

403 E[4] = E[95]&&__STATE_PROG_R[0];<br />

404 if (E[4]) {<br />

405 __STATE_PROG_A5;<br />

406 }<br />

407 E[99] = (E[95]&&!(__STATE_PROG_R[0]))||E[4];<br />

408 E[100] = E[99];<br />

409 E[94] = E[94];<br />

410 E[96] = E[96]&&!(E[34]);<br />

411 E[101] = !(E[31])&&E[96];<br />

412 E[102] = __STATE_PROG_R[1]&&!(__STATE_PROG_R[0]);<br />

327 E[67] = E[52]&&E[25];<br />

328 E[52] = E[52]&&!(E[25]);<br />

329 E[19] = (E[20]&&__STATE_PROG_R[12])||(E[19]&&__STATE_PROG_R[12]);<br />

330 E[14] = ((E[24]||E[47]||E[48])&&E[17]&&E[16])||((E[21]||E[50]||E[51])&&<br />

331 E[15]&&E[14])||E[19]||E[52]||E[67]||E[66];<br />

332 E[57] = E[57]&&E[12]&&__STATE_PROG_R[10];<br />

333 E[18] = __STATE_PROG_R[10]||E[18];<br />

334 E[15] = E[18]||__STATE_PROG_R[11];<br />

335 E[18] = (E[15]&&!(E[18]))||E[57]||E[14];<br />

336 E[11] = !((<br />

337 __STATE_PROG_A1))&&E[11];<br />

338 E[16] = E[11]||(E[15]&&!(__STATE_PROG_R[11]));<br />

339 E[17] = (E[16]||E[12])&&E[18]&&E[12];<br />

340 E[20] = E[7]||E[17];<br />

341 E[68] = E[20]||__STATE_PROG_R[0];<br />

342 E[69] = !(E[9])&&E[68];<br />

343 E[70] = E[69]&&E[12];<br />

344 E[71] = E[70];<br />

345 E[68] = E[9]&&E[68];<br />

346 E[49] = E[17]||E[49]||E[49]||E[49];<br />

347 E[9] = !(E[17])&&E[67];<br />

348 E[72] = E[67];<br />

349 E[67] = E[67];<br />

350 E[73] = E[68]&&(<br />

351 __STATE_PROG_A2);<br />

352 if (E[73]) {<br />

353 __STATE_PROG_A13;<br />

354 }<br />

355 E[46] = E[17]||E[46]||E[46]||E[46];<br />

356 E[74] = !(E[17])&&E[66];<br />

357 E[75] = E[68]&&!((<br />

358 __STATE_PROG_A2));<br />

359 E[75] = E[75]||E[73];<br />

360 E[66] = E[75]||E[66];<br />

361 E[58] = E[68]||E[58];<br />

362 E[59] = E[68]||E[59];<br />

363 E[76] = E[68];<br />

364 E[20] = E[20];<br />

365 E[69] = E[69]&&!(E[12]);<br />

366 E[77] = __STATE_PROG_R[9]&&!(__STATE_PROG_R[0]);<br />

413 E[96] = E[31]&&E[96];<br />

414 E[31] = E[96]&&!((<br />

367 E[78] = E[70];<br />

368 E[79] = E[68];<br />

369 E[80] = _false;<br />

370 E[81] = E[31];


A.4. Control Flow Mapping–Stage Two (C) 169<br />

459 __STATE_PROG_R[11] = E[68]||(!(E[17])&&E[11]);<br />

460 __STATE_PROG_R[12] = (!(E[17])&&E[19])||(!(E[17])&&E[52]);<br />

461 __STATE_PROG_R[13] = E[75]||E[74]||(!(E[46])&&E[48]);<br />

462 __STATE_PROG_R[14] = !(E[46])&&E[47];<br />

463 __STATE_PROG_R[15] = E[75]||E[74]||(!(E[46])&&E[24]);<br />

464 __STATE_PROG_R[16] = E[9]||(!(E[49])&&E[51]);<br />

465 __STATE_PROG_R[17] = !(E[49])&&E[50];<br />

466 __STATE_PROG_R[18] = E[9]||(!(E[49])&&E[21]);<br />

467 __STATE_PROG_R[19] = E[70]||(!(E[7])&&E[61]);<br />

468 __STATE_PROG_R[20] = !(E[7])&&E[62];<br />

469 __STATE_PROG_R[21] = E[70]||(!(E[7])&&E[6]);<br />

470 __STATE_PROG__reset_input();<br />

471 return E[63];<br />

472 }<br />

473<br />

474 /* AUTOMATON RESET */<br />

475<br />

476 int STATE_PROG_reset () {<br />

477 __STATE_PROG_R[0] = _true;<br />

478 __STATE_PROG_R[1] = _false;<br />

479 __STATE_PROG_R[2] = _false;<br />

415 __STATE_PROG_A2));<br />

416 E[103] = E[31]&&(<br />

417 __STATE_PROG_A1);<br />

418 if (E[103]) {<br />

419 __STATE_PROG_A10;<br />

420 }<br />

421 E[104] = E[96]&&(<br />

422 __STATE_PROG_A2);<br />

423 if (E[104]) {<br />

424 __STATE_PROG_A9;<br />

425 }<br />

426 if (_false) {<br />

427 __STATE_PROG_A10;<br />

428 }<br />

429 if (_false) {<br />

430 __STATE_PROG_A9;<br />

431 }<br />

432 E[31] = E[31]&&!((<br />

433 __STATE_PROG_A1));<br />

434 E[31] = E[31]||E[104]||E[103];<br />

435 E[105] = __STATE_PROG_R[8]&&!(__STATE_PROG_R[0]);<br />

436 E[86] = ((E[2]||E[90]||E[89])&&E[93]&&E[91])||((E[28]||E[85]||E[84])&&<br />

437 E[88]&&E[86])||E[102]||E[105]||E[101]||E[97]||E[99]||E[31];<br />

438 E[63] = ((E[11]||E[57]||E[14])&&E[16]&&E[18])||((E[6]||E[62]||E[61])&&<br />

439 E[65]&&E[63])||E[77]||E[69]||E[70]||((E[75]||E[68])&&E[75]&&E[68]);<br />

440 E[92] = __STATE_PROG_R[1]||__STATE_PROG_R[8]||E[87]||E[92];<br />

441 E[15] = __STATE_PROG_R[9]||E[64]||E[15];<br />

442 E[64] = E[92]||E[15];<br />

443 E[63] = ((E[64]&&!(E[92]))||E[86])&&((E[64]&&!(E[15]))||E[63])&&(E[86]||E[63]);<br />

444 E[96] = E[96];<br />

445 E[86] = E[97];<br />

446 E[95] = E[95];<br />

447 E[15] = _false;<br />

448 __STATE_PROG_R[0] = _false;<br />

449 __STATE_PROG_R[1] = E[102]||E[101];<br />

450 __STATE_PROG_R[2] = E[99]||(!(E[35])&&E[89]);<br />

451 __STATE_PROG_R[3] = !(E[35])&&E[90];<br />

452 __STATE_PROG_R[4] = E[99]||(!(E[35])&&E[2]);<br />

453 __STATE_PROG_R[5] = E[97]||(!(E[30])&&E[84]);<br />

454 __STATE_PROG_R[6] = !(E[30])&&E[85];<br />

455 __STATE_PROG_R[7] = E[97]||(!(E[30])&&E[28]);<br />

[...]<br />

498 __STATE_PROG_R[21] = _false;<br />

499 __STATE_PROG__reset_input();<br />

500 return 0;<br />

501 }<br />

A.4.2 Esterel-Compiler Output Optimized <strong>for</strong><br />

Memory Efficiency<br />

173 /* REGISTER VARIABLES */<br />

174<br />

175 struct REGISTERS {<br />

176 unsigned int R0:1;<br />

177 unsigned int R1:1;<br />

178 unsigned int R2:1;<br />

[...]<br />

197 unsigned int R21:1;<br />

198 } __STATE_PROG_bitR;<br />

199<br />

456 __STATE_PROG_R[8] = E[105]||E[31];<br />

457 __STATE_PROG_R[9] = E[77]||E[69];<br />

458 __STATE_PROG_R[10] = !(E[17])&&E[57];


170 Appendix A. OSM Code Generation<br />

343 if (bitE.E22) {<br />

344 __STATE_PROG_A15;<br />

345 }<br />

346 bitE.E23 = bitE.E22;<br />

347 bitE.E24 = bitE.E20&&__STATE_PROG_bitR.R15;<br />

348 bitE.E25 = (__STATE_PROG_A2)&&bitE.E24;<br />

349 if (bitE.E25) {<br />

350 __STATE_PROG_A14;<br />

351 }<br />

352 bitE.E26 = bitE.E25;<br />

353 bitE.E27 = bitE.E12;<br />

354 bitE.E28 = __STATE_PROG_bitR.R7&&!(__STATE_PROG_bitR.R0);<br />

355 bitE.E29 = bitE.E28&&(__STATE_PROG_A2);<br />

356 if (bitE.E29) {<br />

357 __STATE_PROG_A8;<br />

358 }<br />

359 bitE.E28 = bitE.E28&&!((__STATE_PROG_A2));<br />

360 bitE.E30 = bitE.E28&&(__STATE_PROG_A1);<br />

361 bitE.E31 = bitE.E29||bitE.E30;<br />

362 bitE.E32 = bitE.E31;<br />

363 bitE.E33 = bitE.E31;<br />

364 if (bitE.E3) {<br />

365 __STATE_PROG_A6;<br />

366 }<br />

367 bitE.E2 = bitE.E2&&!((__STATE_PROG_A2));<br />

368 bitE.E34 = bitE.E2&&(__STATE_PROG_A1);<br />

369 if (bitE.E34) {<br />

370 __STATE_PROG_A7;<br />

371 }<br />

372 bitE.E35 = bitE.E3||bitE.E34;<br />

373 bitE.E36 = bitE.E35;<br />

374 bitE.E37 = bitE.E34;<br />

375 bitE.E38 = (__STATE_PROG_A1);<br />

376 bitE.E39 = _false;<br />

377 bitE.E40 = _false;<br />

378 bitE.E41 = bitE.E7;<br />

379 bitE.E42 = _false;<br />

380 bitE.E43 = bitE.E22;<br />

381 bitE.E44 = _false;<br />

382 bitE.E45 = bitE.E25;<br />

383 bitE.E46 = bitE.E20&&__STATE_PROG_bitR.R14;<br />

384 bitE.E19 = bitE.E19&&bitE.E12;<br />

385 bitE.E47 = (bitE.E19&&__STATE_PROG_bitR.R14)||(bitE.E46&&bitE.E25&&<br />

386 __STATE_PROG_bitR.R14);<br />

200 /* AUTOMATON ENGINE */<br />

201<br />

202 int STATE_PROG() {<br />

203 /* AUXILIARY VARIABLES */<br />

204<br />

205 static struct {<br />

206 unsigned int E0:1;<br />

207 unsigned int E1:1;<br />

208 unsigned int E2:1;<br />

[...]<br />

310 unsigned int E104:1;<br />

311 unsigned int E105:1;<br />

312 } bitE;<br />

313<br />

314 bitE.E0 = (__STATE_PROG_A2);<br />

315 bitE.E1 = __STATE_PROG_bitR.R0;<br />

316 bitE.E2 = __STATE_PROG_bitR.R4&&!(__STATE_PROG_bitR.R0);<br />

317 bitE.E3 = bitE.E2&&(__STATE_PROG_A2);<br />

318 bitE.E4 = bitE.E3||__STATE_PROG_bitR.R0;<br />

319 bitE.E5 = bitE.E4;<br />

320 bitE.E6 = __STATE_PROG_bitR.R21&&!(__STATE_PROG_bitR.R0);<br />

321 bitE.E7 = bitE.E6&&(__STATE_PROG_A1);<br />

322 bitE.E8 = bitE.E7;<br />

323 bitE.E9 = bitE.E7||__STATE_PROG_bitR.R0;<br />

324 bitE.E10 = bitE.E9;<br />

325 bitE.E11 = __STATE_PROG_bitR.R11&&!(__STATE_PROG_bitR.R0);<br />

326 bitE.E12 = (__STATE_PROG_A1)&&bitE.E11;<br />

327 if (bitE.E12) {<br />

328 __STATE_PROG_A11;<br />

329 }<br />

330 if (bitE.E12) {<br />

331 __STATE_PROG_A12;<br />

332 }<br />

333 bitE.E13 = bitE.E12;<br />

334 bitE.E14 = __STATE_PROG_bitR.R17||__STATE_PROG_bitR.R16;<br />

335 bitE.E15 = __STATE_PROG_bitR.R18||bitE.E14;<br />

336 bitE.E16 = __STATE_PROG_bitR.R14||__STATE_PROG_bitR.R13;<br />

337 bitE.E17 = __STATE_PROG_bitR.R15||bitE.E16;<br />

338 bitE.E18 = __STATE_PROG_bitR.R12||bitE.E15||bitE.E17;<br />

339 bitE.E19 = bitE.E18&&!(__STATE_PROG_bitR.R0);<br />

340 bitE.E20 = bitE.E19&&!(bitE.E12);<br />

341 bitE.E21 = bitE.E20&&__STATE_PROG_bitR.R18;<br />

342 bitE.E22 = bitE.E21&&(__STATE_PROG_A2);


A.4. Control Flow Mapping–Stage Two (C) 171<br />

431 bitE.E65 = (bitE.E64&&!(__STATE_PROG_bitR.R21))||bitE.E6;<br />

432 bitE.E7 = (bitE.E65||bitE.E7)&&bitE.E63&&bitE.E7;<br />

433 bitE.E66 = bitE.E58&&!((__STATE_PROG_A2));<br />

434 bitE.E66 = bitE.E66||bitE.E60;<br />

435 bitE.E52 = !(bitE.E59)&&bitE.E52;<br />

436 bitE.E67 = bitE.E52&&bitE.E25;<br />

437 bitE.E52 = bitE.E52&&!(bitE.E25);<br />

438 bitE.E19 = (bitE.E20&&__STATE_PROG_bitR.R12)||(bitE.E19&&__STATE_PROG_bitR.R12);<br />

439 bitE.E14 = ((bitE.E24||bitE.E47||bitE.E48)&&bitE.E17&&bitE.E16)||<br />

440 ((bitE.E21||bitE.E50||bitE.E51)&&bitE.E15&&bitE.E14)||<br />

441 bitE.E19||bitE.E52||bitE.E67||bitE.E66;<br />

442 bitE.E57 = bitE.E57&&bitE.E12&&__STATE_PROG_bitR.R10;<br />

443 bitE.E18 = __STATE_PROG_bitR.R10||bitE.E18;<br />

444 bitE.E15 = bitE.E18||__STATE_PROG_bitR.R11;<br />

445 bitE.E18 = (bitE.E15&&!(bitE.E18))||bitE.E57||bitE.E14;<br />

446 bitE.E11 = !((__STATE_PROG_A1))&&bitE.E11;<br />

447 bitE.E16 = bitE.E11||(bitE.E15&&!(__STATE_PROG_bitR.R11));<br />

448 bitE.E17 = (bitE.E16||bitE.E12)&&bitE.E18&&bitE.E12;<br />

449 bitE.E20 = bitE.E7||bitE.E17;<br />

450 bitE.E68 = bitE.E20||__STATE_PROG_bitR.R0;<br />

451 bitE.E69 = !(bitE.E9)&&bitE.E68;<br />

452 bitE.E70 = bitE.E69&&bitE.E12;<br />

453 bitE.E71 = bitE.E70;<br />

454 bitE.E68 = bitE.E9&&bitE.E68;<br />

455 bitE.E49 = bitE.E17||bitE.E49||bitE.E49||bitE.E49;<br />

456 bitE.E9 = !(bitE.E17)&&bitE.E67;<br />

457 bitE.E72 = bitE.E67;<br />

458 bitE.E67 = bitE.E67;<br />

459 bitE.E73 = bitE.E68&&(__STATE_PROG_A2);<br />

460 if (bitE.E73) {<br />

461 __STATE_PROG_A13;<br />

462 }<br />

463 bitE.E46 = bitE.E17||bitE.E46||bitE.E46||bitE.E46;<br />

464 bitE.E74 = !(bitE.E17)&&bitE.E66;<br />

465 bitE.E75 = bitE.E68&&!((__STATE_PROG_A2));<br />

466 bitE.E75 = bitE.E75||bitE.E73;<br />

467 bitE.E66 = bitE.E75||bitE.E66;<br />

468 bitE.E58 = bitE.E68||bitE.E58;<br />

469 bitE.E59 = bitE.E68||bitE.E59;<br />

470 bitE.E76 = bitE.E68;<br />

471 bitE.E20 = bitE.E20;<br />

472 bitE.E69 = bitE.E69&&!(bitE.E12);<br />

473 bitE.E77 = __STATE_PROG_bitR.R9&&!(__STATE_PROG_bitR.R0);<br />

474 bitE.E78 = bitE.E70;<br />

387 bitE.E48 = bitE.E20&&__STATE_PROG_bitR.R13;<br />

388 bitE.E48 = (bitE.E46&&!(bitE.E25))||(bitE.E48&&!(bitE.E25)&&<br />

389 __STATE_PROG_bitR.R13)||((bitE.E19||(bitE.E48&&bitE.E25))&&<br />

390 __STATE_PROG_bitR.R13);<br />

391 bitE.E16 = (bitE.E17&&!(bitE.E16))||bitE.E47||bitE.E48;<br />

392 bitE.E24 = !((__STATE_PROG_A2))&&bitE.E24;<br />

393 bitE.E24 = (bitE.E19&&__STATE_PROG_bitR.R15)||bitE.E24;<br />

394 bitE.E17 = (bitE.E17&&!(__STATE_PROG_bitR.R15))||bitE.E24;<br />

395 bitE.E46 = (bitE.E17||bitE.E25)&&bitE.E16&&bitE.E25;<br />

396 bitE.E49 = bitE.E20&&__STATE_PROG_bitR.R17;<br />

397 bitE.E50 = (bitE.E19&&__STATE_PROG_bitR.R17)||(bitE.E49&&bitE.E22&&<br />

398 __STATE_PROG_bitR.R17);<br />

399 bitE.E51 = bitE.E20&&__STATE_PROG_bitR.R16;<br />

400 bitE.E51 = (bitE.E49&&!(bitE.E22))||(bitE.E51&&!(bitE.E22)&&<br />

401 __STATE_PROG_bitR.R16)||(((bitE.E51&&bitE.E22)||bitE.E19)&&<br />

402 __STATE_PROG_bitR.R16);<br />

403 bitE.E14 = (bitE.E15&&!(bitE.E14))||bitE.E50||bitE.E51;<br />

404 bitE.E21 = bitE.E21&&!((__STATE_PROG_A2));<br />

405 bitE.E21 = (bitE.E19&&__STATE_PROG_bitR.R18)||bitE.E21;<br />

406 bitE.E15 = (bitE.E15&&!(__STATE_PROG_bitR.R18))||bitE.E21;<br />

407 bitE.E49 = (bitE.E15||bitE.E22)&&bitE.E14&&bitE.E22;<br />

408 bitE.E52 = bitE.E49||bitE.E46;<br />

409 bitE.E53 = bitE.E52;<br />

410 bitE.E54 = bitE.E25;<br />

411 bitE.E55 = _false;<br />

412 bitE.E56 = bitE.E12;<br />

413 bitE.E57 = __STATE_PROG_bitR.R10&&!(__STATE_PROG_bitR.R0);<br />

414 bitE.E58 = bitE.E57&&!(bitE.E12);<br />

415 bitE.E59 = bitE.E58||bitE.E22;<br />

416 bitE.E52 = bitE.E58||bitE.E52;<br />

417 bitE.E58 = bitE.E59&&bitE.E52;<br />

418 bitE.E60 = bitE.E58&&(__STATE_PROG_A2);<br />

419 if (bitE.E60) {<br />

420 __STATE_PROG_A13;<br />

421 }<br />

422 bitE.E6 = bitE.E6&&!((__STATE_PROG_A1));<br />

423 bitE.E61 = __STATE_PROG_bitR.R19&&!(__STATE_PROG_bitR.R0);<br />

424 bitE.E62 = __STATE_PROG_bitR.R20&&!(__STATE_PROG_bitR.R0);<br />

425 bitE.E61 = (bitE.E62&&!(bitE.E7))||(bitE.E61&&!(bitE.E7)&&<br />

426 __STATE_PROG_bitR.R19)||(bitE.E61&&bitE.E7&&__STATE_PROG_bitR.R19);<br />

427 bitE.E62 = bitE.E62&&bitE.E7&&__STATE_PROG_bitR.R20;<br />

428 bitE.E63 = __STATE_PROG_bitR.R20||__STATE_PROG_bitR.R19;<br />

429 bitE.E64 = bitE.E63||__STATE_PROG_bitR.R21;<br />

430 bitE.E63 = (bitE.E64&&!(bitE.E63))||bitE.E62||bitE.E61;


172 Appendix A. OSM Code Generation<br />

519 bitE.E31 = bitE.E96&&!((__STATE_PROG_A2));<br />

520 bitE.E103 = bitE.E31&&(__STATE_PROG_A1);<br />

521 if (bitE.E103) {<br />

522 __STATE_PROG_A10;<br />

523 }<br />

524 bitE.E104 = bitE.E96&&(__STATE_PROG_A2);<br />

525 if (bitE.E104) {<br />

526 __STATE_PROG_A9;<br />

527 }<br />

528 if (_false) {<br />

529 __STATE_PROG_A10;<br />

530 }<br />

531 if (_false) {<br />

532 __STATE_PROG_A9;<br />

533 }<br />

534 bitE.E31 = bitE.E31&&!((__STATE_PROG_A1));<br />

535 bitE.E31 = bitE.E31||bitE.E104||bitE.E103;<br />

536 bitE.E105 = __STATE_PROG_bitR.R8&&!(__STATE_PROG_bitR.R0);<br />

537 bitE.E86 = ((bitE.E2||bitE.E90||bitE.E89)&&bitE.E93&&bitE.E91)||<br />

538 ((bitE.E28||bitE.E85||bitE.E84)&&bitE.E88&&bitE.E86)||<br />

539 bitE.E102||bitE.E105||bitE.E101||bitE.E97||bitE.E99||bitE.E31;<br />

540 bitE.E63 = ((bitE.E11||bitE.E57||bitE.E14)&&bitE.E16&&bitE.E18)||<br />

541 ((bitE.E6||bitE.E62||bitE.E61)&&bitE.E65&&bitE.E63)||bitE.E77||<br />

542 bitE.E69||bitE.E70||((bitE.E75||bitE.E68)&&bitE.E75&&bitE.E68);<br />

543 bitE.E92 = __STATE_PROG_bitR.R1||__STATE_PROG_bitR.R8||bitE.E87||bitE.E92;<br />

544 bitE.E15 = __STATE_PROG_bitR.R9||bitE.E64||bitE.E15;<br />

545 bitE.E64 = bitE.E92||bitE.E15;<br />

546 bitE.E63 = ((bitE.E64&&!(bitE.E92))||bitE.E86)&&((bitE.E64&&!(bitE.E15))||<br />

547 bitE.E63)&&(bitE.E86||bitE.E63);<br />

548 bitE.E96 = bitE.E96;<br />

549 bitE.E86 = bitE.E97;<br />

550 bitE.E95 = bitE.E95;<br />

551 bitE.E15 = _false;<br />

552 __STATE_PROG_bitR.R0 = _false;<br />

553 __STATE_PROG_bitR.R1 = bitE.E102||bitE.E101;<br />

554 __STATE_PROG_bitR.R2 = bitE.E99||(!(bitE.E35)&&bitE.E89);<br />

555 __STATE_PROG_bitR.R3 = !(bitE.E35)&&bitE.E90;<br />

556 __STATE_PROG_bitR.R4 = bitE.E99||(!(bitE.E35)&&bitE.E2);<br />

557 __STATE_PROG_bitR.R5 = bitE.E97||(!(bitE.E30)&&bitE.E84);<br />

558 __STATE_PROG_bitR.R6 = !(bitE.E30)&&bitE.E85;<br />

559 __STATE_PROG_bitR.R7 = bitE.E97||(!(bitE.E30)&&bitE.E28);<br />

560 __STATE_PROG_bitR.R8 = bitE.E105||bitE.E31;<br />

561 __STATE_PROG_bitR.R9 = bitE.E77||bitE.E69;<br />

562 __STATE_PROG_bitR.R10 = !(bitE.E17)&&bitE.E57;<br />

475 bitE.E79 = bitE.E68;<br />

476 bitE.E80 = _false;<br />

477 bitE.E81 = bitE.E31;<br />

478 bitE.E82 = _false;<br />

479 bitE.E83 = bitE.E35;<br />

480 bitE.E28 = bitE.E28&&!((__STATE_PROG_A1));<br />

481 bitE.E84 = __STATE_PROG_bitR.R5&&!(__STATE_PROG_bitR.R0);<br />

482 bitE.E85 = __STATE_PROG_bitR.R6&&!(__STATE_PROG_bitR.R0);<br />

483 bitE.E84 = (!(bitE.E31)&&bitE.E85)||(!(bitE.E31)&&bitE.E84&&<br />

484 __STATE_PROG_bitR.R5)||(bitE.E31&&bitE.E84&&__STATE_PROG_bitR.R5);<br />

485 bitE.E85 = bitE.E31&&bitE.E85&&__STATE_PROG_bitR.R6;<br />

486 bitE.E86 = __STATE_PROG_bitR.R6||__STATE_PROG_bitR.R5;<br />

487 bitE.E87 = bitE.E86||__STATE_PROG_bitR.R7;<br />

488 bitE.E86 = (bitE.E87&&!(bitE.E86))||bitE.E85||bitE.E84;<br />

489 bitE.E88 = (bitE.E87&&!(__STATE_PROG_bitR.R7))||bitE.E28;<br />

490 bitE.E30 = bitE.E31&&(bitE.E88||bitE.E29||bitE.E30)&&bitE.E86;<br />

491 bitE.E89 = __STATE_PROG_bitR.R2&&!(__STATE_PROG_bitR.R0);<br />

492 bitE.E90 = __STATE_PROG_bitR.R3&&!(__STATE_PROG_bitR.R0);<br />

493 bitE.E89 = (!(bitE.E35)&&bitE.E90)||(!(bitE.E35)&&bitE.E89&&<br />

494 __STATE_PROG_bitR.R2)||(bitE.E35&&bitE.E89&&__STATE_PROG_bitR.R2);<br />

495 bitE.E90 = bitE.E35&&bitE.E90&&__STATE_PROG_bitR.R3;<br />

496 bitE.E91 = __STATE_PROG_bitR.R3||__STATE_PROG_bitR.R2;<br />

497 bitE.E92 = bitE.E91||__STATE_PROG_bitR.R4;<br />

498 bitE.E91 = (bitE.E92&&!(bitE.E91))||bitE.E90||bitE.E89;<br />

499 bitE.E2 = bitE.E2&&!((__STATE_PROG_A1));<br />

500 bitE.E93 = (bitE.E92&&!(__STATE_PROG_bitR.R4))||bitE.E2;<br />

501 bitE.E35 = bitE.E35&&(bitE.E93||bitE.E3||bitE.E34)&&bitE.E91;<br />

502 bitE.E94 = bitE.E30||bitE.E35;<br />

503 bitE.E95 = bitE.E94||__STATE_PROG_bitR.R0;<br />

504 bitE.E96 = !(bitE.E4)&&bitE.E95;<br />

505 bitE.E97 = bitE.E96&&bitE.E34;<br />

506 bitE.E98 = bitE.E97;<br />

507 bitE.E95 = bitE.E4&&bitE.E95;<br />

508 bitE.E4 = bitE.E95&&__STATE_PROG_bitR.R0;<br />

509 if (bitE.E4) {<br />

510 __STATE_PROG_A5;<br />

511 }<br />

512 bitE.E99 = (bitE.E95&&!(__STATE_PROG_bitR.R0))||bitE.E4;<br />

513 bitE.E100 = bitE.E99;<br />

514 bitE.E94 = bitE.E94;<br />

515 bitE.E96 = bitE.E96&&!(bitE.E34);<br />

516 bitE.E101 = !(bitE.E31)&&bitE.E96;<br />

517 bitE.E102 = __STATE_PROG_bitR.R1&&!(__STATE_PROG_bitR.R0);<br />

518 bitE.E96 = bitE.E31&&bitE.E96;


A.5. Compilation 173<br />

7 -ansi -B "test" "C:\Temp\ssc2356.ssc" gcc -I.. -Wall -c -o prog.o<br />

8 prog.c prog.c: In function ‘main’: prog.c:74: warning: implicit<br />

9 declaration of function ‘exit’ prog.c:79: warning: implicit<br />

10 declaration of function ‘STATE_PROG_I_e’ prog.c:82: warning: implicit<br />

11 declaration of function ‘STATE_PROG_I_f’ gcc -I.. -Wall -c -o<br />

12 noncanonical_termio.o ../noncanonical_termio.c gcc -I.. -Wall -c -o<br />

13 test.o test.c test.c:46: warning: ‘__STATE_PROG_PActionArray’ defined<br />

14 but not used gcc prog.o test.o noncanonical_termio.o -o prog<br />

15 kasten@laptop ~/projects/kasten/src/osm/osmc2/test ><br />

563 __STATE_PROG_bitR.R11 = bitE.E68||(!(bitE.E17)&&bitE.E11);<br />

564 __STATE_PROG_bitR.R12 = (!(bitE.E17)&&bitE.E19)||(!(bitE.E17)&&bitE.E52);<br />

565 __STATE_PROG_bitR.R13 = bitE.E75||bitE.E74||(!(bitE.E46)&&bitE.E48);<br />

566 __STATE_PROG_bitR.R14 = !(bitE.E46)&&bitE.E47;<br />

567 __STATE_PROG_bitR.R15 = bitE.E75||bitE.E74||(!(bitE.E46)&&bitE.E24);<br />

568 __STATE_PROG_bitR.R16 = bitE.E9||(!(bitE.E49)&&bitE.E51);<br />

569 __STATE_PROG_bitR.R17 = !(bitE.E49)&&bitE.E50;<br />

570 __STATE_PROG_bitR.R18 = bitE.E9||(!(bitE.E49)&&bitE.E21);<br />

571 __STATE_PROG_bitR.R19 = bitE.E70||(!(bitE.E7)&&bitE.E61);<br />

572 __STATE_PROG_bitR.R20 = !(bitE.E7)&&bitE.E62;<br />

573 __STATE_PROG_bitR.R21 = bitE.E70||(!(bitE.E7)&&bitE.E6);<br />

A.5.2 Makefile<br />

574 __STATE_PROG__reset_input();<br />

575 return bitE.E63;<br />

576 }<br />

1 # -----------------------------------------------------------<br />

2 # files and directories<br />

3 # -----------------------------------------------------------<br />

577<br />

578 /* AUTOMATON RESET */<br />

4 TARGET = prog<br />

5<br />

6 OSM_SRCS = test.osm<br />

7 C_SRCS = prog.c noncanonical_termio.c<br />

579<br />

580 int STATE_PROG_reset () {<br />

581 __STATE_PROG_bitR.R0 = 1;<br />

582 __STATE_PROG_bitR.R1 = 0;<br />

583 __STATE_PROG_bitR.R2 = 0;<br />

8 # generated sources<br />

9 GENE_SRCS = $(OSM_SRCS:.osm=.strl)<br />

10 GENC_SRCS = $(OSM_SRCS:.osm=.c)<br />

11<br />

12 SRCS = $(GENC_SRCS) $(C_SRCS)<br />

13 OBJS = $(SRCS:.c=.o)<br />

[...]<br />

602 __STATE_PROG_bitR.R21 = 0;<br />

603 __STATE_PROG__reset_input();<br />

604 return 0;<br />

605 }<br />

14<br />

15 VPATH = ..<br />

16<br />

17 # -----------------------------------------------------------<br />

18 # programs<br />

19 # -----------------------------------------------------------<br />

20 # the esterel compiler requires c:\temp to exist!<br />

21 ESTERELC = $(ESTEREL)/bin/esterel.exe<br />

22 # java must be in PATH<br />

23 JAVA = java<br />

24 # OSM.class must be in CLASSPATH<br />

A.5 Compilation<br />

A.5.1 Example Compilation <strong>for</strong> the BTnode<br />

Emulation<br />

25 OSM = $(JAVA) OSM<br />

26<br />

27 RM = rm -Rf<br />

28 MV = mv<br />

29 CP = cp<br />

30 ECHO = echo<br />

1 kasten@laptop ~/projects/kasten/src/osm/osmc2/test > make # #<br />

2 generating esterel-files from test.osm # java OSM test.osm Checking<br />

3 ... done. Writing Esterel code and C include file to "test.strl" and<br />

4 "test.h" ... done. # # generating c-files from test.strl #<br />

5 C:\PROGRA~1\Esterel/bin/esterel.exe -Lc:-ansi -causal test.strl<br />

6 C:\Cygwin\home\kasten\projects\kasten\src\osm\osmc2\test\10>occ -sscc


174 Appendix A. OSM Code Generation<br />

75<br />

76 # --------------------------------------------------------------------------<br />

77 # secondary targets, misc<br />

78 # --------------------------------------------------------------------------<br />

79 .SECONDARY: $(addprefix $(basename $(OSM_SRCS)),.o .strl .c)<br />

80<br />

31<br />

32 # -----------------------------------------------------------<br />

33 # rules<br />

34 # -----------------------------------------------------------<br />

35 .PHONY : all clean<br />

36<br />

81 SUFFIXES = .osm .strl<br />

37 # all: prog<br />

38 prog: prog.o test.o noncanonical_termio.o<br />

39 prog.o: test.c<br />

40<br />

41 %.strl: %.osm<br />

42 #<br />

43 # generating esterel-files from $<<br />

44 #<br />

45 $(OSM) $<<br />

46<br />

47 %.c: %.strl<br />

48 #<br />

49 # generating c-files from $<<br />

50 #<br />

51 $(ESTERELC) -Lc:-ansi -causal $<<br />

52<br />

53 clean:<br />

54 $(RM) *~ *.class *.o<br />

55 $(RM) $(GENC_SRCS)<br />

56 $(RM) $(GENE_SRCS)<br />

57 $(RM) $(TARGET) $(TARGET).exe<br />

58 $(RM) $(OSM_SRCS:.osm=.c)<br />

59 $(RM) $(OSM_SRCS:.osm=.h)<br />

60<br />

61 # --------------------------------------------------------------------------<br />

62 # Flags<br />

63 # --------------------------------------------------------------------------<br />

64 # (un)comment as appropriate<br />

65<br />

66 # BTnode<br />

67 # CFLAGS += -I$(BTNODE_INSTALL_DIR) -I.. -Wall<br />

68 # LDLIBS += -L$(BTNODE_INSTALL_DIR)/linux -lBTNsys<br />

69<br />

70 # Linux and Cygwin<br />

71 CFLAGS += -I.. -Wall<br />

72 LDLIBS +=<br />

73<br />

74 LDFLAGS +=


12 * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE VANDERBILT<br />

13 * UNIVERSITY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.<br />

14 *<br />

15 * THE UNIVERSITY OF VIRGINIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,<br />

16 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY<br />

17 * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS<br />

18 * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF VIRGINIA HAS NO OBLIGATION TO<br />

19 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.<br />

20 */<br />

21<br />

22 // Authors: Brian Blum, Tian He, Liqian Luo<br />

23<br />

24 /* =========================================================<br />

25 *<br />

B Implementations of the<br />

EnviroTrack Group<br />

Management<br />

26 * ========================================================= */<br />

27 module GroupManagementM {<br />

28 provides {<br />

29 interface GroupManagement;<br />

30 interface StdControl;<br />

31 }<br />

32 uses {<br />

33 interface RoutingSendByBroadcast as SendMsgByBct;<br />

34 interface RoutingReceive as ReceiveRoutingMsg;<br />

35<br />

36 interface TimedLeds;<br />

37 interface Random;<br />

38 interface Leds;<br />

39 interface Localization as Local;<br />

40 interface Link;<br />

This Appendix shows the NesC and OSM implementations of EnviroTrack’s<br />

Group Management in Sections B.1 and B.2, respectively.<br />

In order to make both implementations comparable, only<br />

control-flow code is shown in the NesC code. Code representing<br />

actions has been replaced by placeholders op1(), op2, etc., other<br />

code has been replaced by [...]. The implementations of actions<br />

are not shown; they are equal in both implementations.<br />

41 }<br />

42 } /* end module GroupManagementM */<br />

43<br />

B.1 NesC Implementation<br />

44 /* =========================================================<br />

45 *<br />

46 * ========================================================= */<br />

1 /*<br />

2 * Copyright (c) 2004-2006, University of Virginia<br />

47 implementation {<br />

48<br />

3 * All rights reserved.<br />

4 *<br />

49 /* variables */<br />

50 GMStatus _GMStatus;<br />

51 uint16_t _generalTimer;<br />

5 * Permission to use, copy, modify, and distribute this software and its<br />

6 * documentation <strong>for</strong> any purpose, without fee, and without written agreement is<br />

7 * hereby granted, provided that the above copyright notice, the following<br />

8 * two paragraphs and the author appear in all copies of this software.<br />

52 // [...]<br />

53<br />

9 *<br />

54 /* function prototypes */<br />

55 inline result_t GMSend( /* [...]*/, GMMsgType type );<br />

10 * IN NO EVENT SHALL THE UNIVERSITY OF VIRGINIA BE LIABLE TO ANY PARTY FOR<br />

11 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT


176 Appendix B. Implementations of the EnviroTrack Group Management<br />

100 _generalTimer = wait_random();<br />

101 break;<br />

102 } // end case MEMBER<br />

103<br />

104 case NEWCANDIDATE: {<br />

105 GMSend( /* [...] */, CANDIDATE );<br />

106 _generalTimer = wait_recruit();<br />

107<br />

108 if( /* [...] */ ) {<br />

109 op2();<br />

110 _GMStatus.status = LEADERCANDIDATE;<br />

111 _generalTimer = wait_random();<br />

56 inline void initGMStatus();<br />

57<br />

58 // the following functions replace long defines from the original code<br />

59 inline int wait_random();<br />

60 inline int wait_receive();<br />

61 inline int wait_recruit();<br />

62<br />

63 /* tasks */<br />

64 task void ProcessRecuritMessage();<br />

65<br />

66 /* --------------------------------------------------------- *<br />

67 * Commands<br />

68 * --------------------------------------------------------- */<br />

69 command result_t StdControl.init() {<br />

112 }<br />

113 break;<br />

114 } // end case NEWCANDIDATE<br />

115<br />

116 case LEADERCANDIDATE: {<br />

117 op3();<br />

118 GMSend( /* [...] */, CANDIDATE );<br />

119 _GMStatus.status = LEADER;<br />

120 _generalTimer = wait_random();<br />

121 break;<br />

122 } // end case LEADERCANDIDATE<br />

123<br />

124 case LEADER: {<br />

125 GMSend( /* [...] */, RECRUIT );<br />

126 _generalTimer = wait_recruit();<br />

127 op4();<br />

128 break;<br />

129 } // end case LEADER<br />

130<br />

131 case RESIGNINGLEADER: {<br />

132 GMSend( /* [...] */, RESIGN );<br />

133 if( /* [...] */ ) {<br />

134 op5();<br />

135 _generalTimer = wait_recruit(); // self transition<br />

136 } else {<br />

137 op6();<br />

138 initGMStatus(); // transition to FREE<br />

139 }<br />

140 break;<br />

141 } // end case RESIGNLEADER<br />

142<br />

70 initGMStatus();<br />

71 _generalTimer = 0;<br />

72 // [...]<br />

73 return SUCCESS;<br />

74 }<br />

75<br />

76 command result_t StdControl.start() { /* [...] */ }<br />

77<br />

78 command result_t StdControl.stop() { /* [...] */ }<br />

79<br />

80 command result_t GroupManagement.fireHeartBeat() {<br />

81<br />

82 if( _GMStatus.status == FREE )<br />

83 return SUCCESS;<br />

84<br />

85 if( _generalTimer > 0 )<br />

86 _generalTimer --;<br />

87<br />

88 if( _generalTimer


B.1. NesC Implementation 177<br />

188 } // end case MEMBER<br />

189<br />

190 case NEWCANDIDATE: {<br />

191 _GMStatus.status = FREE;<br />

192 break;<br />

193 } // end case NEWCANDIDATE<br />

194<br />

195 case LEADERCANDIDATE: {<br />

196 if( /* [...] */ ) {<br />

197 _GMStatus.status = FREE;<br />

198 } else {<br />

199 _GMStatus.status = FOLLOWER;<br />

200 _generalTimer = wait_threshold();<br />

201 }<br />

202 break;<br />

203 } // end case LEADERCANDIDATE<br />

204<br />

205 case LEADER: {<br />

206 GMSend( /* [...] */, RESIGN );<br />

207 op10();<br />

208 _GMStatus.status = RESIGNINGLEADER;<br />

209 _generalTimer = wait_recruit();<br />

144 } // end switch( _GMStatus.status )<br />

145<br />

146 } // end if( _generalTimer


178 Appendix B. Implementations of the EnviroTrack Group Management<br />

276 if( RxBuffer->type == RECRUIT ) {<br />

277 op17();<br />

278 _GMStatus.status = MEMBER;<br />

279 _generalTimer = wait_receive();<br />

280 } else if( RxBuffer->type == CANDIDATE ) {<br />

281 if( /* [...] */ ) {<br />

282 op18();<br />

283 _GMStatus.status = MEMBER;<br />

284 _generalTimer = wait_receive();<br />

285 }<br />

286 }<br />

287 break;<br />

288 } // end case NEWCANDIDATE<br />

289<br />

290 case LEADERCANDIDATE: {<br />

291 if( RxBuffer->type == RECRUIT ) {<br />

292 if( /* [...] */ ) {<br />

293 op19();<br />

294 _GMStatus.status = MEMBER;<br />

295 _generalTimer = wait_receive();<br />

296 }<br />

297 } else if( RxBuffer->type == CANDIDATE ) {<br />

298 if( /* [...] */ ) {<br />

299 op20();<br />

300 _GMStatus.status = MEMBER;<br />

301 _generalTimer = wait_receive();<br />

302 }<br />

303 }<br />

304 break;<br />

305 } // end case LEADERCANDIDATE<br />

306<br />

307 case LEADER: {<br />

308 if( RxBuffer->type == RECRUIT ) {<br />

309 if( /* [...] */ ) {<br />

310 if( /* [...] */ ) {<br />

311 op21();<br />

312 _GMStatus.status = MEMBER;<br />

313 _generalTimer = wait_receive();<br />

314 } else {<br />

315 GMSend( /* [...] */, RECRUIT );<br />

316 op22();<br />

317 _generalTimer = wait_recruit();<br />

232 op11();<br />

233 }<br />

234<br />

235 /* --------------------------------------------------------- *<br />

236 * TASKS<br />

237 * --------------------------------------------------------- */<br />

238 task void ProcessRecuritMessage() {<br />

239 GMPacket* RxBuffer;<br />

240 op12();<br />

241<br />

242 switch( _GMStatus.status ) {<br />

243<br />

244 case FREE: {<br />

245 if( (RxBuffer->type == RECRUIT) || (RxBuffer->type == RESIGN) ) {<br />

246 op13();<br />

247 _GMStatus.status = FOLLOWER;<br />

248 _generalTimer = wait_threshold();<br />

249 }<br />

250 break;<br />

251 } // end case FREE<br />

252<br />

253 case FOLLOWER: {<br />

254 if( (RxBuffer->type == RECRUIT) || (RxBuffer->type == RESIGN) ) {<br />

255 op14();<br />

256 _generalTimer = wait_threshold();<br />

257 }<br />

258 break;<br />

259 } // end case FOLLOWER<br />

260<br />

261 case MEMBER: {<br />

262 if( RxBuffer->type == RECRUIT ) {<br />

263 op15();<br />

264 _generalTimer = wait_receive();<br />

265 } else if( RxBuffer->type == RESIGN ) {<br />

266 if( /* [...] */ ) {<br />

267 op16();<br />

268 _GMStatus.status = LEADERCANDIDATE;<br />

269 _generalTimer = wait_random();<br />

318 }<br />

319 }<br />

270 }<br />

271 }<br />

272 break;<br />

273 } // end case MEMBER<br />

274<br />

275 case NEWCANDIDATE: {


B.1. NesC Implementation 179<br />

320 } else if( (RxBuffer->type == RESIGN) ||<br />

321 (RxBuffer->type == CANDIDATE) ) {<br />

322 if( /* [...] */ ) {<br />

323 GMSend( /* [...] */, RECRUIT );<br />

324 op23();<br />

325 _generalTimer = wait_recruit();<br />

326 }<br />

327 }<br />

328 break;<br />

329 } // end case LEADER<br />

330<br />

331 case RESIGNINGLEADER: {<br />

332 if( (RxBuffer->type == RECRUIT) ||<br />

333 (RxBuffer->type == CANDIDATE) ) {<br />

334 if( /* [...] */ ) {<br />

335 op24();<br />

336 _GMStatus.status = FOLLOWER;<br />

337 _generalTimer = wait_threshold();<br />

338 }<br />

339 }<br />

340 break;<br />

341 } // end case RESIGNINGLEADER<br />

342<br />

343 default: break;<br />

344 } // end switch( _GMStatus.status )<br />

345<br />

346 // [...]<br />

347<br />

348 } // end task ProcessRecuritMessage()<br />

349<br />

350 } // end module implementation


180 Appendix B. Implementations of the EnviroTrack Group Management<br />

B.2 OSM Implementation<br />

32 }<br />

33<br />

34 state LEADERCANDIDATE {<br />

35 onEntry: / reset_timer( wait_random() );<br />

36 leave[...] / /* */ -> FOLLOWER,<br />

37 [else] / /* */ -> FREE;<br />

38 timeout / op3(), send_candidate_msg() -> LEADER;<br />

39 recriut_msg[...] / op19() -> MEMBER;<br />

40 candidate_msg[...] / op12(), op20() -> MEMBER;<br />

1 template state GROUP_MANAGEMENT {<br />

2 onEntry: / initialize_timer(), init_group_mgmt();<br />

3<br />

4 initial state FREE {<br />

5 onEntry: / cancel_timer();<br />

6 join / /**/ -> NEWCANDIDATE;<br />

7 recruit_msg OR resign_msg / op12(), op13() -> FOLLOWER;<br />

41 }<br />

42<br />

8 }<br />

9<br />

43 state LEADER {<br />

44 onEntry: LEADERCANDIDATE -> / reset_timer( wait_random() );<br />

45 onEntry: RESIGNINGLEADER -> / reset_timer( wait_recruit() );<br />

46 onEntry: self -> / reset_timer( wait_recruit() );<br />

47 timeout / op4(), send_recruit_msg() -> self;<br />

48 leave / op10(), send_resign_msg() -> RESIGNINGLEADER;<br />

49 recruit_msg[... AND ...] / op12(), op21() -> MEMBER;<br />

50 recruit_msg[... AND !...] / op12(), op22(), send_recruit_msg() -> self;<br />

51 resign_msg OR candidate_msg / op12(), op23(), send_recruit_msg() -> self;<br />

10 state FOLLOWER {<br />

11 onEntry: / reset_timer( wait_threshold() );<br />

12 join / op7() -> MEMBER;<br />

13 timeout / op11() -> FREE;<br />

14 recruit_msg OR resign_msg / op12(), op14() -> self;<br />

15 }<br />

16<br />

52 }<br />

53<br />

17 state MEMBER {<br />

18 onEntry: / reset_timer( wait_receive() );<br />

19 leave / op9() -> FOLLOWER;<br />

20 resign_msg[...] / op16() -> LEADERCANDIDATE;<br />

21 timeout / op1() -> LEADERCANDIDATE;<br />

22 recruit_msg / op12(), op15() -> self;<br />

54 state RESIGNINGLEADER {<br />

55 onEntry: / reset_timer( wait_recruit() );<br />

56 timeout[...] / op5(), send_resign_msg() -> FREE,<br />

57 [else] / op6(), send_resign_msg() -> self;<br />

58 join / op8(), send_recruit_msg() -> LEADER;<br />

23 }<br />

24<br />

59 recruit_msg[...] OR<br />

60 candidate_msg[...] / op12(), op24() -> FOLLOWER;<br />

61 }<br />

62<br />

63 onPreemption: cancel_timer();<br />

64 } // end state GROUP_MANAGEMENT<br />

25 state NEWCANDIDATE {<br />

26 onEntry: FREE -> join / reset_timer( wait_random() );<br />

27 timeout[...] / op2(), send_candidate_msg() -> LEADERCANDIDATE;<br />

28 [else] / reset_timer( wait_recruit() );<br />

29 leave / /* */ -> FREE;<br />

30 recriut_msg / op17() -> MEMBER;<br />

31 candidate_msg[...] / op12(), op18() -> MEMBER;


Bibliography<br />

[1] Proceedings of the First International Conference on Mobile Systems, Applications,<br />

and Services (MobiSys 2003), San Francisco, CA, USA, 2003. USENIX.<br />

[2] SenSys ’03: Proceedings of the 1st International Conference on Embedded Networked<br />

<strong>Sensor</strong> Systems, New York, NY, USA, 2003. ACM Press.<br />

[3] Proceedings of the 2nd International Conference on Mobile Systems, Applications,<br />

and Services (MobiSys 2004), Boston, MA, USA, 2004. ACM Press.<br />

[4] SenSys ’04: Proceedings of the 2nd International Conference on Embedded Networked<br />

<strong>Sensor</strong> Systems, New York, NY, USA, 2004. ACM Press.<br />

[5] IPSN ’05: Proceedings of the Fourth International Symposium on In<strong>for</strong>mation<br />

Processing in <strong>Sensor</strong> <strong>Networks</strong>, Piscataway, NJ, USA, April 25-27 2005. IEEE<br />

Press.<br />

[6] T. Abdelzaher, B. Blum, D. Evans, J. George, S. George, L. Gu, T. He,<br />

C. Huang, P. Nagaraddi, S. Son, P. Sorokin, J. Stankovic, and A. Wood.<br />

EnviroTrack: Towards an environmental computing paradigm <strong>for</strong> distributed<br />

sensor networks. In Proc. of 24th International Conference on Distributed<br />

Computing Systems (ICDCS), Tokyo, Japan, March 2004.<br />

[7] Tarek Abdelzaher, John Stankovic, Sang Son, Brian Blum, Tian He, Anthony<br />

Wood, and Chanyang Lu. A Communication Architecture and <strong>Programming</strong><br />

Abstractions <strong>for</strong> Real-Time Embedded <strong>Sensor</strong> <strong>Networks</strong>. In<br />

Workshop on Data Distribution <strong>for</strong> Real-Time Systems (in conjunction with<br />

ICDCS 2003), Providence, Rhode Island, 2003. Invited Paper.<br />

[8] H. Abrach, S. Bhatti, J. Carlson, H. Dai, J. Rose, A. Sheth, B. Shucker,<br />

J. Deng, and R. Han. Mantis: system support <strong>for</strong> multimodal networks<br />

of in-situ sensors. In Proceedings of the 2nd ACM International Conference on<br />

<strong>Wireless</strong> <strong>Sensor</strong> <strong>Networks</strong> and Applications (WSNA’03), pages 50–59. ACM<br />

Press, 2003.<br />

[9] Atul Adya, Jon Howell, Marvin Theimer, William J. Bolosky, and John R.<br />

Douceur. Cooperative task management without manual stack management.<br />

In Proceedings of the General Track: 2002 USENIX Annual Technical<br />

Conference, pages 289–302. USENIX Association, 2002.<br />

[10] Charles André. Synccharts: a visual representation of reactive behaviors.<br />

Technical report, I3S, Sophia-Antipolis, France, October 1995.


182 Bibliography<br />

[11] Charles André. Representation and analysis of reactive behaviors: A synchronous<br />

approach. In Proc. CESA ’96, Lille, France, July 1996.<br />

[12] Stavros Antifakos, Florian Michahelles, and Bernt Schiele. Proactive instructions<br />

<strong>for</strong> furniture assembly. In UbiComp ’02: Proceedings of the 4th<br />

international conference on Ubiquitous Computing, pages 351–360, London,<br />

UK, 2002. Springer-Verlag.<br />

[13] Felice Balarin, Massimiliano Chiodo, Paolo Giusto, Harry Hsieh, Attila<br />

Jurecska, Luciano Lavagno, Claudio Passerone, Alberto Sangiovanni-<br />

Vincentelli, Ellen Sentovich, Kei Suzuki, and Bassam Tabbara. Hardwaresoftware<br />

co-design of embedded systems: the POLIS approach. Kluwer Academic<br />

Publishers, 1997.<br />

[14] Can Basaran, Sebnem Baydere, Giancarlo Bongiovanni, Adam Dunkels,<br />

M. Onur Ergin, Laura Marie Feeney, Isa Hacioglu, Vlado Handziski,<br />

Andreas Köpke, Maria Lijding, Gaia Maselli, Nirvana Meratnia, Chiara<br />

Petrioli, Silvia Santini, Lodewijk van Hoesel, Thiemo Voigt, and Andrea<br />

Zanella. Research integration: Plat<strong>for</strong>m survey—critical evaluation<br />

of research plat<strong>for</strong>ms <strong>for</strong> wireless sensor networks, June 2006.<br />

Available from: http://www.embedded-wisents.org/studies/<br />

survey_wp2.html.<br />

[15] R. Beckwith, D. Teibel, and P. Bowen. Pervasive computing and proactive<br />

agriculture. In Adjunct Proc. PERVASIVE 2004, Vienna, Austria, April 2004.<br />

[16] Michael Beigl, Hans-Werner Gellersen, and Albrecht Schmidt. Media<br />

cups: Experiences with design and use of computer-augmented everyday<br />

objects. Computer <strong>Networks</strong>, Special Issue on Pervasive Computing, Elsevier,<br />

2000.<br />

[17] Albert Benveniste, Paul Caspi, Stephen A. Edwards, Nicolas Halbwachs,<br />

Paul Le Guernic, and Robert de Simone. The synchronous languages 12<br />

years later. Proceedings of the IEEE, 91(1):64–83, 2003.<br />

[18] Gérard Berry. The Esterel v5 Language Primer, Version v5 91. Centre de<br />

Mathématiques Appliquées Ecole des Mines and INRIA, 2004 Route des<br />

Lucioles, 06565 Sophia-Antipolis, July 2000.<br />

[19] Gérard Berry and the Esterel Team. The Esterel v5.91 System Manual. IN-<br />

RIA, 2004 Route des Lucioles, 06565 Sophia-Antipolis, June 2000.<br />

[20] Jan Beutel. Networked <strong>Wireless</strong> Embedded Systems: Design and Deployment.<br />

PhD thesis, Eidgenössische Technische Hochschule Zürich, Zurich,<br />

Switzerland, 2005.<br />

[21] Jan Beutel, Oliver Kasten, Friedemann Mattern, Kay Römer, Frank Siegemund,<br />

and Lothar Thiele. Prototyping <strong>Wireless</strong> <strong>Sensor</strong> Network Applications<br />

with BTnodes. In Karl et al. [71], pages 323–338.<br />

[22] Jan Beutel, Oliver Kasten, and Matthias Ringwald. Poster Abstract:<br />

BTnodes – a distributed plat<strong>for</strong>m <strong>for</strong> sensor nodes. In SenSys ’03 [2], pages<br />

292–293.


Bibliography 183<br />

[23] Brian M. Blum, Prashant Nagaraddi, Anthony D. Wood, Tarek F. Abdelzaher,<br />

Sang Hyuk Son, and Jack Stankovic. An entity maintenance and connection<br />

service <strong>for</strong> sensor networks. In MobiSys [1].<br />

[24] Philippe Bonet. The Hogthrob Project. ESF Exploratory Workshop on<br />

<strong>Wireless</strong> <strong>Sensor</strong> <strong>Networks</strong>, ETH Zurich, Zurich, Switzerland, April 1-2,<br />

2004. Available from: http://www.hogthrob.dk/index.htm.<br />

[25] A. Boulis and M. B. Srivastava. Design and implementation of a framework<br />

<strong>for</strong> efficient and programmable sensor networks. In MobiSys [1].<br />

[26] Frédéric Boussinot and Robert de Simone. The ESTEREL language. Proc.<br />

of the IEEE, 79(9):1293–1304, September 1991.<br />

[27] Ed Bryan O’Sullivan. The history of threads, in comp.os.research:<br />

Frequently answered questions. Online. Visited 2007-08-03. Available<br />

from: http://www.faqs.org/faqs/os-research/part1/<br />

preamble.html.<br />

[28] Z. Butler, P. Corke, R. Peterson, and D. Rus. Networked cows: Virtual<br />

fences <strong>for</strong> controlling cows. In WAMES 2004, Boston, USA, June 2004.<br />

[29] Elaine Cheong, Judy Liebman, Jie Liu, and Feng Zhao. Tinygals: A programming<br />

model <strong>for</strong> event-driven embedded systems. In SAC ’03: Proceedings<br />

of the 2003 ACM symposium on Applied computing, pages 698–704,<br />

New York, NY, USA, 2003. ACM Press.<br />

[30] Sung-Eun Choi and E. Christopher Lewis. A study of common pitfalls in<br />

simple multi-threaded programs. In SIGCSE ’00: Proceedings of the thirtyfirst<br />

SIGCSE technical symposium on Computer science education, pages 325–<br />

329, New York, NY, USA, 2000. ACM Press.<br />

[31] Intel Corporation. Intel mote. Visited 2007-08-03. Available from: http:<br />

//www.intel.com/research/exploratory/motes.htm.<br />

[32] National Research Council. Embedded Everywhere: A Research Agenda <strong>for</strong><br />

Networked Systems of Embedded Computers. National Academy Press, Washington,<br />

DC, USA, 2001. Available from: http://www.nap.edu/html/<br />

embedded_everywhere/.<br />

[33] Robert Cravotta. Reaching down: 32-bit processors aim <strong>for</strong> 8 bits. EDN<br />

Website (www.edn.com), February 2005. Available from: http://www.<br />

edn.com/contents/images/502421.pdf.<br />

[34] Inc. Crossbow Technology. Berkeley motes. Visited 2007-08-03. Available<br />

from: http://www.xbow.com/Products/wproductsoverview.<br />

aspx.<br />

[35] Christian Decker, Albert Krohn, Michael Beigl, and Tobias Zimmer. The<br />

particle computer system. In IPSN ’05 [5], pages 443–448.


184 Bibliography<br />

[36] Cormac Duffy, Utz Roedig, John Herbert, and Cormac Sreenan. An experimental<br />

comparison of event driven and multi-threaded sensor node<br />

operating systems. In PERCOMW ’07: Proceedings of the Fifth IEEE International<br />

Conference on Pervasive Computing and Communications Workshops,<br />

pages 267–271, Washington, DC, USA, 2007. IEEE Computer Society.<br />

[37] Adam Dunkels, Björn Grönvall, and Thiemo Voigt. Contiki - a lightweight<br />

and flexible operating system <strong>for</strong> tiny networked sensors. In Proceedings of<br />

the First IEEE Workshop on Embedded Networked <strong>Sensor</strong>s 2004 (IEEE EmNetS-<br />

I), Tampa, Florida, USA, November 2004.<br />

[38] Adam Dunkels, Oliver Schmidt, and Thiemo Voigt. Using Protothreads<br />

<strong>for</strong> <strong>Sensor</strong> Node <strong>Programming</strong>. In Proceedings of the RE-<br />

ALWSN’05 Workshop on Real-World <strong>Wireless</strong> <strong>Sensor</strong> <strong>Networks</strong>, Stockholm,<br />

Sweden, June 2005. Available from: http://www.sics.se/~adam/<br />

dunkels05using.pdf.<br />

[39] Virantha Ekanayake, Clinton Kelly IV, and Rajit Manohar. An ultra lowpower<br />

processor <strong>for</strong> sensor networks. In Eleventh International Conference<br />

on Architectural Support <strong>for</strong> <strong>Programming</strong> Languages and Operating Systems,<br />

volume 32, pages 27–36, Boston, USA, December 2004. ACM Press.<br />

[40] EnviroTrack. Web page. Visited 2007-08-03. Available from: http://<br />

www.cs.uiuc.edu/homes/lluo2/EnviroTrack/.<br />

[41] Deborah Estrin, Ramesh Govindan, John Heidemann, and Satish Kumar.<br />

Next century challenges: scalable coordination in sensor networks. In MobiCom<br />

’99: Proceedings of the 5th annual ACM/IEEE international conference<br />

on Mobile computing and networking, pages 263–270, New York, NY, USA,<br />

1999. ACM Press.<br />

[42] Christian Frank. Role-based Configuration of <strong>Wireless</strong> <strong>Sensor</strong> <strong>Networks</strong>.<br />

PhD thesis, Eidgenössische Technische Hochschule Zürich, Department<br />

of Computer Science, Institute <strong>for</strong> Pervasive Computing, Zürich, Switzerland,<br />

June 2007.<br />

[43] D. D. Gajski, J. Zhu, R. Domer, A. Gerstlauer, and S. Zhao. SpecC: Specification<br />

Language and Methodology. Kluwer Academic Publishers, January<br />

2000.<br />

[44] Daniel D. Gajski and Loganath Ramachandran. Introduction to high-level<br />

synthesis. IEEE Des. Test, 11(4):44–54, October 1994.<br />

[45] David Gay, Philip Levis, Robert von Behren, Matt Welsh, Eric Brewer, and<br />

David E. Culler. The nesc language: A holistic approach to networked<br />

embedded systems. In Proceedings of the ACM SIGPLAN 2003 conference on<br />

<strong>Programming</strong> language design and implementation, pages 1–11. ACM Press,<br />

2003.<br />

[46] Hans-Werner Gellersen, Albrecht Schmidt, and Michael Beigl. Multisensor<br />

context-awareness in mobile devices and smart artifacts. Mobile<br />

<strong>Networks</strong> and Applications (MONET), 7(5):341–351, October 2002.


Bibliography 185<br />

[47] ScatterWeb GmbH. Scatterweb. Web page. Visited 2007-08-03. Available<br />

from: http://www.scatterweb.com/.<br />

[48] Ben Greenstein, Eddie Kohler, and Deborah Estrin. A sensor network application<br />

construction kit (SNACK). In SenSys ’04 [4], pages 69–80.<br />

[49] The OMG Object Management Group. OMG Unified <strong>Model</strong>ing Language<br />

Specification. Mar 2003. Version 1.5.<br />

[50] Nicolas Halbwachs. Synchronous programming of reactive systems. In<br />

CAV ’98: Proceedings of the 10th International Conference on Computer Aided<br />

Verification, pages 1–16, London, UK, 1998. Springer-Verlag.<br />

[51] Chih-Chieh Han, Ram Kumar, Roy Shea, and Mani Srivastava. <strong>Sensor</strong><br />

network software update management: A survey. International Journal of<br />

Network Management, 15(4):283–294, July 2005.<br />

[52] Chih-Chieh Han, Ram Kumar Rengaswamy, Roy Shea, Eddie Kohler, and<br />

Mani Srivastava. SOS: A Dynamic Operating System <strong>for</strong> <strong>Sensor</strong> Nodes. In<br />

MobiSys 2005, pages 163–176, Seattle, WA, USA, 2005. ACM Press.<br />

[53] David Harel. <strong>State</strong>charts: A visual <strong>for</strong>malism <strong>for</strong> complex systems. Science<br />

of Computer <strong>Programming</strong>, 8(3):231–274, June 1987.<br />

[54] Tian He, Sudha Krishnamurthy, John A. Stankovic, Tarek Abdelzaher,<br />

Liqian Luo, Radu Stoleru, Ting Yan, Lin Gu, Jonathan Hui, and Bruce<br />

Krogh. Energy-efficient surveillance system using wireless sensor networks.<br />

In MobiSys [3], pages 270–283.<br />

[55] Wendi B. Heinzelman, Amy L. Murphy, Hervaldo S. Carvalho, and<br />

Mark A. Perillo. Middleware to support sensor network applications.<br />

IEEE Network, 18(1):6–14, 2004.<br />

[56] Wendi Rabiner Heinzelman, Anantha Chandrakasan, and Hari Balakrishnan.<br />

Energy-efficient communication protocol <strong>for</strong> wireless microsensor<br />

networks. In HICSS ’00: Proceedings of the 33rd Hawaii International Conference<br />

on System Sciences-Volume 8, page 8020, Washington, DC, USA, 2000.<br />

IEEE Computer Society.<br />

[57] Jason L. Hill and David E. Culler. Mica: A wireless plat<strong>for</strong>m <strong>for</strong> deeply<br />

embedded networks. IEEE Micro, 22(6):12–24, November 2002.<br />

[58] Jason L. Hill, Mike Horton, Ralph Kling, and Lakshman Krishnamurthy.<br />

The plat<strong>for</strong>ms enabling wireless sensor networks. Communications of the<br />

ACM, 47(6):41–46, June 2004.<br />

[59] Jason L. Hill, Robert Szewczyk, A. Woo, S. Hollar, David E. Culler, and<br />

Kristofer S. J. Pister. System architecture directions <strong>for</strong> networked sensors.<br />

In Proceedings of the 9th International Conference on Architectural Support <strong>for</strong><br />

<strong>Programming</strong> Languages and Operating Systems ASPLOS-IX, pages 93–104,<br />

Cambridge MA, USA, November 2000.


186 Bibliography<br />

[60] Jason Lester Hill. System architecture <strong>for</strong> wireless sensor networks. PhD thesis,<br />

University of Cali<strong>for</strong>nia, Berkeley, 2003.<br />

[61] Seth Edward-Austin Hollar. Cots dust, large scale models <strong>for</strong> smart dust.<br />

Master’s thesis, University of Cali<strong>for</strong>nia, Berkeley, 2000.<br />

[62] Lars Erik Holmquist, Friedemann Mattern, Bernt Schiele, Petteri<br />

Alahuhta, Michael Beigl, and Hans-W. Gellersen. Smart-its friends: A<br />

technique <strong>for</strong> users to easily establish connections between smart artefacts.<br />

In Proc. Ubicomp 2001, pages 116–122, Atlanta, USA, September 2001.<br />

Springer-Verlag.<br />

[63] ARGO Homepage. Web page. Visited 2007-08-03. Available from: http:<br />

//www.argo.ucsd.edu/.<br />

[64] Wen Hu, Van Nghia Tran, Nirupama Bulusu, Chun Tung Chou, Sanjay<br />

Jha, and Andrew Taylor. The design and evaluation of a hybrid sensor<br />

network <strong>for</strong> Cane-toad monitoring. In IPSN ’05 [5], page 71.<br />

[65] J. P. Hubaux, Th. Gross, J. Y. Le Boudec, and M. Vetterli. Towards sel<strong>for</strong>ganized<br />

mobile ad hoc networks: the Terminodes project. IEEE Communications<br />

Magazine, 31(1):118–124, 2001.<br />

[66] Clinton Kelly IV, Virantha Ekanayake, and Rajit Manohar. Snap: A sensornetwork<br />

asynchronous processor. In ASYNC ’03: Proceedings of the 9th International<br />

Symposium on Asynchronous Circuits and Systems, page 24. IEEE<br />

Computer Society, 2003.<br />

[67] Philo Juang, Hidekazu Oki, Yong Wang, Margaret Martonosi, Li-Shiuan<br />

Peh, and Daniel Rubenstein. Energy-efficient computing <strong>for</strong> wildlife tracking:<br />

design tradeoffs and early experiences with ZebraNet. In ASPLOS-<br />

X: Proceedings of the 10th international conference on Architectural support <strong>for</strong><br />

programming languages and operating systems, pages 96–107, San Jose, Cali<strong>for</strong>nia,<br />

USA, October 2002. ACM Press.<br />

[68] J. M. Kahn, R. H. Katz, and Kristofer S. J. Pister. Emerging challenges:<br />

Mobile networking <strong>for</strong> smart dust. Journal of Communications and <strong>Networks</strong>,<br />

2(3):188–196, September 2000.<br />

[69] Cornelia Kappler and Georg Riegel. A real-world, simple wireless sensor<br />

network <strong>for</strong> monitoring electrical energy consumption. In Karl et al. [71],<br />

pages 339–352.<br />

[70] Holger Karl and Andreas Willig. Protocols and Architectures <strong>for</strong> <strong>Wireless</strong><br />

<strong>Sensor</strong> <strong>Networks</strong>. John Wiley & Sons, April 2005.<br />

[71] Holger Karl, Andreas Willig, and Adam Wolisz, editors. <strong>Wireless</strong> <strong>Sensor</strong><br />

<strong>Networks</strong>, First European Workshop, EWSN 2004, Berlin, Germany, January<br />

19-21, 2004, Proceedings, volume 2920 of Lecture Notes in Computer Science<br />

(LNCS), Berlin, Germany, January 2004. Springer-Verlag.


Bibliography 187<br />

[72] Oliver Kasten. <strong>Programming</strong> wireless sensor nodes—a state-based model<br />

<strong>for</strong> complex applications. GI/ITG KuVS Fachgespräch Systemsoftware<br />

für Pervasive Computing, Universiät Stuttgart, Germany, October, 14-15<br />

2004.<br />

[73] Oliver Kasten and Marc Langheinrich. First Experiences with Bluetooth<br />

in the Smart-Its Distributed <strong>Sensor</strong> Network. In Workshop on Ubiqitous<br />

Computing and Communication, PACT 2001, October 2001.<br />

[74] Oliver Kasten and Kay Römer. Beyond Event Handlers: <strong>Programming</strong><br />

<strong>Wireless</strong> <strong>Sensor</strong>s with Attributed <strong>State</strong> Machines. In IPSN ’05 [5], pages<br />

45–52.<br />

[75] Tae-Hyung Kim and Seongsoo Hong. <strong>State</strong> machine based operating system<br />

architecture <strong>for</strong> wireless sensor networks. In Parallel and Distributed<br />

Computing: Applications and Technologies : 5th International Conference, PD-<br />

CAT, LNCS 3320, pages 803–806, Singapore, December 2004. Springer-<br />

Verlag.<br />

[76] I3S Laboratory. Syncchart. Web page. Visited 2007-03-10. Available from:<br />

http://www.i3s.unice.fr/sports/SyncCharts/.<br />

[77] Edward A. Lee. What’s ahead <strong>for</strong> embedded software? Computer,<br />

33(9):18–26, 2000.<br />

[78] Martin Leopold, Mads Bondo Dydensborg, and Philippe Bonnet. Bluetooth<br />

and sensor networks: a reality check. In SenSys ’03 [2], pages 103–<br />

113.<br />

[79] Philip Levis and David E. Culler. Maté: A tiny virtual machine <strong>for</strong> sensor<br />

networks. ACM SIGOPS Operating Systems Review, 36(5):85–95, December<br />

2002.<br />

[80] J. Liu, M. Chu, J. Reich, and F. Zhao. <strong>State</strong>-centric programming <strong>for</strong> sensoractuator<br />

network systems. Pervasive Computing, IEEE, 2(4):50–62, 2003.<br />

[81] Ting Liu, Christopher M. Sadler, Pei Zhang, and Margaret Martonosi. Implementing<br />

software on resource-constrained mobile sensors: experiences<br />

with Impala and ZebraNet. In MobiSys 2004 [3], pages 256–269.<br />

[82] AXONN LLC. AXTracker. Web page. Visited 2005-10-01. Available from:<br />

http://www.axtracker.com/.<br />

[83] S. R. Madden, M. J. Franklin, J. M. Hellerstein, and W. Hong. Tag: a tiny<br />

aggregation service <strong>for</strong> ad-hoc sensor networks. In OSDI 2002, Boston,<br />

USA, December 2002.<br />

[84] Samuel Madden, Michael J. Franklin, Joseph M. Hellerstein, and Wei<br />

Hong. The design of an acquisitional query processor <strong>for</strong> sensor networks.<br />

In SIGMOD ’03: Proceedings of the 2003 ACM SIGMOD international conference<br />

on Management of data, pages 491–502, New York, NY, USA, June 2003.<br />

ACM Press.


188 Bibliography<br />

[85] Alan Mainwaring, David Culler, Joseph Polastre, Robert Szewczyk, and<br />

John Anderson. <strong>Wireless</strong> sensor networks <strong>for</strong> habitat monitoring. In<br />

WSNA ’02: Proceedings of the 1st ACM international workshop on <strong>Wireless</strong><br />

sensor networks and applications, pages 88–97, New York, NY, USA, 2002.<br />

ACM Press.<br />

[86] M. Maroti, G. Simon, A. Ledeczi, and J. Sztipanovits. Shooter localization<br />

in urban terrain. Computer, 37(8):60–61, August 2004.<br />

[87] Ian W. Marshall, Christopher Roadknight, Ibiso Wokoma, and Lionel<br />

Sacks. Self-organising sensor networks. In UbiNet 2003, London, UK,<br />

September 2003.<br />

[88] Kirk Martinez, Royan Ong, and Jane Hart. Glacsweb: a sensor network <strong>for</strong><br />

hostile environments. In Proceedings of The First IEEE Communications Society<br />

Conference on <strong>Sensor</strong> and Ad Hoc Communications and <strong>Networks</strong>, pages<br />

81–87, Santa Clara, CA, USA, October 2004.<br />

[89] William P. McCartney and Nigamanth Sridhar. Abstractions <strong>for</strong> safe concurrent<br />

programming in networked embedded systems. In SenSys ’06:<br />

Proceedings of the 4th international conference on Embedded networked sensor<br />

systems, pages 167–180, New York, NY, USA, 2006. ACM Press.<br />

[90] W.M. Merrill, F. Newberg, K. Sohrabi, W. Kaiser, and G. Pottie. Collaborative<br />

networking requirements <strong>for</strong> unattended ground sensor systems. In<br />

Aerospace Conference, March 2003.<br />

[91] Gordon E. Moore. Cramming more components onto integrated circuits.<br />

Electronics, 38(8), April 1965.<br />

[92] Jogesh K. Muppala. Experience with an embedded systems software<br />

course. SIGBED Rev., 2(4):29–33, 2005.<br />

[93] Sanjiv Narayan, Frank Vahid, and Daniel D. Gajski. Translating system<br />

specifications to VHDL. In EURO-DAC ’91: Proceedings of the conference on<br />

European design automation, pages 390–394. IEEE Computer Society Press,<br />

1991.<br />

[94] John Ousterhout. Why threads are a bad idea (<strong>for</strong> most purposes). In<br />

USENIX Winter Technical Conference, January 1996. Available from: http:<br />

//home.pacbell.net/ouster/threads.ppt.<br />

[95] Axel Poignè, Matthew Morley, Olivier Maffeïs, Leszek Holenderski, and<br />

Reinhard Budde. The Synchronous Approach to Designing Reactive Systems.<br />

Formal Methods in System Design, 12(2):163–187, 1998.<br />

[96] R. Riem-Vis. Cold chain management using an ultra low power wireless<br />

sensor network. In WAMES 2004, Boston, USA, June 2004.<br />

[97] Hartmut Ritter, Min Tian, Thiemo Voigt, and Jochen H. Schiller. A highly<br />

flexible testbed <strong>for</strong> studies of ad-hoc network behaviour. In LCN, pages<br />

746–752. IEEE Computer Society, 2003.


Bibliography 189<br />

[98] Kay Römer, Christian Frank, Pedro José Marrón, and Christian Becker.<br />

Generic role assignment <strong>for</strong> wireless sensor networks. In Proceedings of<br />

the 11th ACM SIGOPS European Workshop, pages 7–12, Leuven, Belgium,<br />

September 2004.<br />

[99] Shad Roundy, Dan Steingart, Luc Frechette, Paul K. Wright, and Jan M.<br />

Rabaey. Power sources <strong>for</strong> wireless sensor networks. In Karl et al. [71],<br />

pages 1–17.<br />

[100] Kay Römer. Tracking real-world phenomena with smart dust. In Karl et al.<br />

[71], pages 28–43.<br />

[101] Kay Römer. Time Synchronization and Localization in <strong>Sensor</strong> <strong>Networks</strong>.<br />

PhD thesis, Eidgenössische Technische Hochschule Zürich, Department<br />

of Computer Science, Institute <strong>for</strong> Pervasive Computing, Zürich, Switzerland,<br />

May 2005.<br />

[102] Andrew Seitz, Derek Wilson, and Jennifer L. Nielsen. Testing pop-up<br />

satellite tags as a tool <strong>for</strong> identifying critical habitat <strong>for</strong> Pacific halibut<br />

(Hippoglossus stenolepis) in the Gulf of Alaska. Exxon Valdez Oil Spill<br />

Restoration Project Final Report (Restoration Project 01478), U.S. Geological<br />

Survey, Alaska Biological Science Center, Anchorage, Alaska, September<br />

2002.<br />

[103] Jack Shandle. More <strong>for</strong> less: Stable future <strong>for</strong> 8-bit microcontrollers.<br />

TechOnLine Website, August 2004. Visited 2007-08-03. Available<br />

from: http://www.techonline.com/community/ed_resource/<br />

feature_article/36930.<br />

[104] Alan C. Shaw. Communicating real-time state machines. IEEE Trans. Softw.<br />

Eng., 18(9):805–816, September 1992.<br />

[105] Saurabh Shukla, Nirupama Bulusu, and Sanjay Jha. Cane-toad Monitoring<br />

in Kakadu National Park Using <strong>Wireless</strong> <strong>Sensor</strong> <strong>Networks</strong>. In Network<br />

Research Workshop 2004 (18th APAN Meetings), Cairns, Australia, July 2004.<br />

[106] Frank Siegemund. Smart-its on the internet – integrating smart<br />

objects into the everyday communication infrastructure, September<br />

2002. Available from: http://www.vs.inf.ethz.ch/publ/papers/<br />

smartits-demo-note-siegemund.pdf.<br />

[107] Frank Siegemund, Christian Floerkemeier, and Harald Vogt. The value of<br />

handhelds in smart environments. In Personal and Ubiquitous Computing<br />

Journal. Springer-Verlag, October 2004.<br />

[108] Abraham Silberschatz, Peter Baer Galvin, and Greg Gagne. Operating System<br />

Concepts. John Wiley & Sons, Inc., New York, NY, USA, 6th edition,<br />

2001.<br />

[109] Gyula Simon, Miklós Marót, Ákos Lédeczi, György Balogh, Branislav<br />

Kusy, András Nádas, Gábor Pap, János Sallai, and Ken Frampton. <strong>Sensor</strong><br />

network-based countersniper system. In SenSys ’04 [4], pages 1–12.


190 Bibliography<br />

[110] Karsten Strehl, Lothar Thiele, Matthias Gries, Dirk Ziegenbein, Rolf Ernst,<br />

and Jürgen Teich. Funstate—an internal design representation <strong>for</strong> codesign.<br />

IEEE Transactions on Very Large Scale Integration (VSLI) Systems,<br />

9(4):524–544, 2001.<br />

[111] John Suh and Mike Horton. Powering sensor networks - current technology<br />

overview. IEEE Potentials, 23(3):35–38, August 2004.<br />

[112] Robert Szewczyk, Eric Osterweil, Joseph Polastre, Michael Hamilton, Alan<br />

Mainwaring, and Deborah Estrin. Habitat monitoring with sensor networks.<br />

Commun. ACM, 47(6):34–40, 2004.<br />

[113] Esterel Technologies. Esterel Compiler (INRIA Ecoles des Mines Academic<br />

Compiler). Web page. Visited 2006-06-22. Available from:<br />

http://www.esterel-technologies.com/technology/demos/<br />

demos.html.<br />

[114] F. Vahid, S. Narayan, and D. D. Gajski. SpecCharts: A VHDL Front-End<br />

<strong>for</strong> Embedded Systems. IEEE Transactions on Computer-Aided Design of Integrated<br />

Circuits and Systems, 14(6):694–706, June 1995.<br />

[115] Robert von Behren, Jeremy Condit, and Eric Brewer. Why events are a bad<br />

idea (<strong>for</strong> high-concurrency servers). In Proceedings of HotOS IX: The 9th<br />

Workshop on Hot Topics in Operating Systems, pages 19–24, Lihue, Hawaii,<br />

USA, May 2003. The USENIX Association.<br />

[116] H. Wang, L. Yip, D. Maniezzo, J. C. Chen, R. E. Hudson, J. Elson, and<br />

K. Yao. A <strong>Wireless</strong> Time-Synchronized COTS <strong>Sensor</strong> Plat<strong>for</strong>m: Applications<br />

to Beam<strong>for</strong>ming. In In Proceedings of IEEE CAS Workshop on <strong>Wireless</strong><br />

Communications and Networking, Pasadena, CA, September 2002.<br />

[117] Hanbiao Wang, Jeremy Elson, Lewis Girod, Deborah Estrin, and Kung<br />

Yao. Target classification and localization in habitat monitoring. In Proceedings<br />

of IEEE International Conference on Acoustics, Speech, and Signal Processing<br />

(ICASSP 2003), Hong Kong, China, April 2003.<br />

[118] Hanbiao Wang, Deborah Estrin, and Lewis Girod. Preprocessing in a<br />

tiered sensor network <strong>for</strong> habitat monitoring. EURASIP Journal of Applied<br />

Signal Processing (EURASIP JASP), (4):392–401, March 2003.<br />

[119] B. A. Warneke, M. D. Scott, B. S. Leibowitz, L. Zhou, C. L. Bellew, J. A. Chediak,<br />

J. M. Kahn, B. E. Boser, and Kristofer S. J. Pister. An autonomous 16<br />

cubic mm solar-powered node <strong>for</strong> distributed wireless sensor networks.<br />

In IEEE <strong>Sensor</strong>s, Orlando, USA, June 2002.<br />

[120] Brett Warneke, Matt Last, Brian Liebowitz, and Kristofer S. J. Pister. Smart<br />

dust: Communicating with a cubic-millimeter computer. IEEE Computer,<br />

45(1):44–51, January 2001.<br />

[121] Andrzej Wasowski and Peter Sestoft. On the <strong>for</strong>mal semantics of visual-<br />

STATE statecharts. Technical Report TR-2002-19, IT University of Copenhagen,<br />

Denmark, September 2002.


Bibliography 191<br />

[122] Ya Xu, John Heidemann, and Deborah Estrin. Geography-in<strong>for</strong>med energy<br />

conservation <strong>for</strong> ad hoc routing. In MobiCom ’01: Proceedings of the<br />

7th annual international conference on Mobile computing and networking, pages<br />

70–84. ACM Press, 2001.<br />

[123] ETH Zurich. BTnodes - A Distributed Environment <strong>for</strong> Prototyping Ad<br />

Hoc <strong>Networks</strong>. Web page. Visited 2007-08-03. Available from: http:<br />

//www.btnode.ethz.ch/.<br />

[124] The Smart-Its Project. Web page. Visited 2007-08-03. Available from:<br />

http://www.smart-its.org.<br />

[125] TinyOS. Web page. Visited 2007-08-03. Available from: http://webs.<br />

cs.berkeley.edu/tos/.


192 Bibliography


Curriculum Vitae<br />

Oliver Kasten<br />

Personal Data<br />

Date of Birth July 10, 1969<br />

Birthplace Balingen, Germany<br />

Citizenship German<br />

Education<br />

1976–1981 Erich-Kästner-Schule, Pfungstadt, Germany<br />

1981–1987 Holbein-Gymnasium, Augsburg, Germany<br />

1987–1989 Schuldorf Bergstrasse, Seeheim-Jugenheim, Germany<br />

June 1989 Abitur<br />

1991–1999 Study of Computer Science at<br />

Technische Universität Darmstadt, Germany<br />

1998 Visiting student at<br />

International Computer Science Institute (ICSI), Berkeley, USA<br />

April 1999 Diplom In<strong>for</strong>matiker<br />

2000–2007 Ph.D. Student at the Department of Computer Science,<br />

ETH Zurich, Switzerland<br />

Civil Service<br />

1989–1990 Malteser Hilfsdienst, Augsburg, Germany<br />

1990–1991 Alten- und Pflegeheim Tannenberg, Seeheim-Jugenheim,<br />

Germany<br />

Employment<br />

1999-2005 Research Assistant at the Department of Computer Science,<br />

ETH Zurich, Switzerland<br />

2005-2006 Software Engineer, Swissphoto AG, Regensdorf, Switzerland<br />

since 2006 Senior Researcher, SAP Research (Schweiz), Zurich, Switzerland

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!