***In progress ***
A TinyOS networked sensor device application is described by a graph
of software components, the lowest of which simply encapsulate the physical
hardware. Components have state, contained in their TOS frame,
an
interface, specifying what commands it implements, what events it signals,
what commands it uses, and what events it handles, and its internal logic,
including the functions implementing its interface and additional threads.
The combination of interface-based composition and internal concurrency
and state provides a smooth intermixing of hardware and software functionality.
There are three aspects of programming networked sensor devices using TinyOS:
Composing an application involves specifying a collection of components and 'wiring ' the command and event relationships between components. TOS currently provides a set of tools that allow component composition to be described using textual editors. We also have support for using schmatic editing tools such as Workview Viewlogic to produce structural VHDL (only a small subset is used).
You should start with a working .desc file, such as those contained in the apps directory. The .desc file has two parts
Components have two files that describe them 1)A .comp file which contains the interface specification for the component. 2)A .c file that contains the implementation of the component. The interface description contains 5 parts.
list
of commands it ACCEPTS
list
of events it SIGNALS
list
of commands it USES
list
of events it HANDLES
list of INTERNAL methods – not used to compose components but automatically placed in header files
Here is an example of a a component description file.
/*AM.comp*/
ACCEPTS{
char AM_SEND_MSG(char addr,char type, char* data);
char AM_POWER(char mode);
char AM_INIT();
};
SIGNALS{
char AM_MSG_REC(char type, char* data);
char AM_MSG_SEND_DONE(char success);
};
HANDLES{
char AM_TX_PACKET_DONE(char success);
char AM_RX_PACKET_DONE(char* packet);
};
USES{
char AM_SUB_TX_PACKET(char* data);
void AM_SUB_POWER(char mode);
char AM_SUB_INIT();
};
The following is drawn from the example apps/blink.vhd
The list of components is simple:
include modules{
MAIN;
BLINK;
CLOCK;
LEDS;
};
The body shows how the ports are all wired together. The names don't matter, but it sure helps to make them meaningful. Here we see that the MAIN_SUB_INIT is wired to BLINK_INIT. BLINK_SUB_INIT is wired to CLOCK_INIT. If there is more than one IN port on a net, the tools will create the fan-out function automatically.
BLINK:BLINK_INIT MAIN:MAIN_SUB_INIT
BLINK:BLINK_APP_DONE MAIN:MAIN_SUB_SEND_DONE
BLINK:BLINK_LEDy_on LEDS:YELLOW_LED_ON
BLINK:BLINK_LEDy_off LEDS:YELLOW_LED_OFF
BLINK:BLINK_LEDr_on LEDS:RED_LED_ON
BLINK:BLINK_LEDr_off LEDS:RED_LED_OFF
BLINK:BLINK_LEDg_on LEDS:GREEN_LED_ON
BLINK:BLINK_LEDg_off LEDS:GREEN_LED_OFF
BLINK:BLINK_SUB_INIT CLOCK:CLOCK_INIT
BLINK:BLINK_CLOCK_EVENT CLOCK:CLOCK_FIRE_EVENT
Most lower-level components provide an initialization command and a power management command.
You may have noticed an extra signal, AM_DISP. It is magic and
described below. If you would rather follow the simple BLINK
example through, skip to Building a Application
Component.
These applications also needs a stack of components stretching down from AM to the physical media. The AM component is generic to a wide range of applications, so you will seldom need to change it. In order to assist with the repetitive use of the same set of components, we have the ability to specify complex components that are an assembly of basic components. One such component is the GENERIC_COMM component which contains a complete RFM radio stack from the Active Messages layer down to the bit processing layer.
A high-level application component will typically implement the upper initialization commands, forward them to lower components as appropriate and then operate in an event driven mode, as defined by the collection of event handlers. The rules are these. Component FOO is described by FOO.c. The declarations for each of the ports appear in the associated .h file and the associated functions appear in the C file. The names used are the port names. The C file will provide a function for every IN port. It will invoke functions associated with OUT ports, using the port name. Commands and events return control information and possibly data. 0 is treated as failure. Non-zero indicates success. This is discussed more with respect to event-based data pumps.
A component should not need to name functions in other components. If it does, the modularity is broken. The tools will translate internal names to external names, as specified by the .desc file.
For the simple BLINK example above, we have the associated include file BLINK.h containing:
#ifndef __blinkHEAD__
#define __blinkHEAD__
#include "tos.h"
#include "blink.h"
//ACCEPTS:
char TOS_COMMAND(BLINK_INIT)();
char TOS_COMMAND(BLINK_START)();
//SIGNALS:
//HANDLES
void BLINK_CLOCK_EVENT();
//USES
char TOS_COMMAND(BLINK_SUB_INIT)(char interval);
#endif
It must include the tos.h header file.
MakefilePC provides the hooks for building a emulation version of the NSD application for debugging purposes. It even emulates the radio. In the future, this support will be incoporated more transparently into lower layers.
It builds up some handy utilty functions, such as:
void LEDy_on()
{
#ifdef FULLPC
printf("Y");
#else
CLR_BLINK_LED1();
#endif
}
It declares a frame to hold its internal state. Variable state
is only visible within this component and referenced as VAR(state).
#define TOS_FRAME_TYPE BLINK_frame
TOS_FRAME_BEGIN(BLINK_frame) {
char state;
}
TOS_FRAME_END(BLINK_frame);
The implementation of the BLINK_INIT turns on all the LEDs and in turn initializes the clock component. Notice, it refers to the internal name BLINK_SUB_INIT, which is bound to CLOCK_INIT through the vBINIT signal.
char TOS_COMMAND(BLINK_INIT)(){
LEDy_on(); LEDr_on(); LEDg_on();
TOS_COMMAND(BLINK_SUB_INIT)(0x03);
/* initialize clock component */
VAR(state) = 0;
return 1;
}
The BLINK_START command, which is invoked after initialization completes, turns all the LEDs off and completes, returning success.
char TOS_COMMAND(BLINK_START)(){
LEDy_off(); LEDr_off(); LEDg_off();
return 1;
}
The TOS scheduler puts the hardware into sleep mode when there is no pending tasks. It remains sensitive to interrupts, such as the real-time clock. BLINK handles this clock event. It increments a 3-bit counter and sets the LEDs accordingly.
/* Clock Event Handler:
update LED state as 3-bit counter and set LEDs to match
*/
void BLINK_CLOCK_EVENT(){
char state = VAR(state) = (VAR(state)+1) & 7;
if (state & 1) LEDy_on(); else LEDy_off();
if (state & 2) LEDg_on(); else LEDg_off();
if (state & 4) LEDr_on(); else LEDr_off();
The BLINK compenent shows the command/event driven control aspect of
Tiny OS and the component modularity. More sophisticated components
demonstrate the use of the command/event protocol for data pumps for streaming
data across one or more component layers and the use of TOS threads to
provide internal concurrency. In the high-level application
component, these capabilities are project up simply as Active Message operations:
send command and arrival handler. These are illustrated by a simple
example CHIRP that periodically takes a sensor reading and broadcasts it
on the network.
Internal concurrency