include modules{
MAIN;
COUNTER;
INT_TO_LEDS;
INT_TO_RFM;
CLOCK;
};
As you've seen before, MAIN is a generic prelude system component used in most applications. CLOCK is a system component that accepts an INIT command to set its event frequency and signals a periodic clock event once initialized. You may notice that none of these components are in the application directory, they are all shared among several application. The logic is convey by the way they are wired together. You may also notice the LEDS does not even appear. This illustrates how description files may be used to introduce multicomponent abstractions - they are hierarchical.
COUNTER uses a TOS_FRAME to hold a counter state variable.
#define TOS_FRAME_TYPE COUNTER_frame
TOS_FRAME_BEGIN(COUNTER_frame) {
char state;
}
TOS_FRAME_END(COUNTER_frame);
It accepts INIT and START commands from main to initialize its operation.
char TOS_COMMAND(COUNTER_INIT)(){
VAR(state) = 0; /* initialize output component */
return TOS_CALL_COMMAND(COUNTER_SUB_OUTPUT_INIT)();
}
char TOS_COMMAND(COUNTER_START)(){
/* initialize clock component and start event processing
*/
return TOS_CALL_COMMAND(COUNTER_SUB_CLOCK_INIT)(tick4ps);
}
void TOS_EVENT(COUNTER_CLOCK_EVENT)(){
VAR(state) ++;
TOS_CALL_COMMAND(COUNTER_OUTPUT)(VAR(state));
}
The first novel aspect of COUNTER is that COUNTER_OUTPUT is wired into two output components: tos/shared/INT_TO_LEDS.c and tos/shared/INT_TO_RFM.c.
INT_TO_LEDS is a simple output device, which places the lower three bits of the integer value onto the LEDS. This gives us the behavior of the countig variant of BLINK.
COUNTER:COUNTER_OUTPUT INT_TO_LEDS:INT_TO_LEDS_OUTPUT INT_TO_RFM:INT_TO_RFM_OUTPUT
A description file may fanout commands or events to multiple components. The program generation tools handle the actual fanout.
If you examine tos/shared/INT_TO_LEDS.comp you will see a new construct:
TOS_MODULE INT_TO_LEDS;
JOINTLY IMPLEMENTED_BY INT_TO_LEDS;
A component may be the root of a subgraph, described
by the associated description file. This is declared in the comp
file using
JOINTLY IMPLEMENTED BY name;
The named file provides the subgraph rooted at the component.
Here tos/shared/INT_TO_LEDS.desc brings in one subcomponent, LED, and provides all the wiring to the primitive LED functions.
COUNTER also handles the event generated by asynchronous output devices upon completion.
COUNTER:COUNTER_OUTPUT_COMPLETE INT_TO_RFM:INT_TO_RFM_COMPLETE
/* Output Completion Event Handler
Indicate that notification was successful */
char TOS_EVENT(COUNTER_OUTPUT_COMPLETE)(char
success)
{
return 1;
}
INT_TO_RFM ( tos/shared/INT_TO_RFM.c
) is the real point of this lesson - now nicely abstracted from the
higher level application. It's job is to send a packet containing
the output value to message handlers on all neighboring nodes.
Messages in TinyOS follow the Active Message (AM) model, so when a packet is injected into the network it names a handler that will be invoked on the recipient nodes. This fits especially nicely in the TinyOS event-driven environment, because message arrival is just another kind of event.
In any messaging layer, there are 5 aspects involved in successful transmission and reception (1) specifying what message is to be sent, (2) specifying who is to receive it, (3) determining when the storage associated with the source message can be reused, (4) providing buffering for the incoming message, and (5) processing the message. Tiny Active Messages are no exception, however, the storage management is more constrained than is typical and the events are more natural.
TOS message buffers are contained in frames and must be declared using the declaration type TOS_Msg. This provides a clean way to perform encapsulation as the packet moves through the stack without copying.
INT_TO_RFM declares a TOS_Msg type variable called, data, inside the application frame.
#define TOS_FRAME_TYPE INT_TO_RFM_frame
TOS_FRAME_BEGIN(INT_TO_RFM_frame)
{
char pending;
TOS_Msg data;
} TOS_FRAME_END(INT_TO_RFM_frame);
The TOS_Msg structure, defined in tos/include/MSG.h , has a field "data", which is a character pointer pointing to the beginning of the message's payload. The payload is currently standardized to be 30 bytes long, although this is a configuration parameter.
You can bang away at the character string and produce unreadable message processing code, but a much better way is to let the compiler deal with all the packaging. Use a C structure such as int_to_led_msg to describe the format of the packet payload.
typdef struct{
char val;
int src;
}int_to_led_msg;
INT_TO_RFM constructs and sends a message when an upper component such as COUNTER, issues a command.
char TOS_COMMAND(INT_TO_RFM_OUTPUT)(int
val)
{
...
return 0;
}
To place the data into the payload, we first declare a int_to_led_msg pointer variable, assign it to the address of the start of the payload, and fill in the value of each field.
int_to_led_msg* message = (int_to_led_msg*)VAR(data).data;
...
message->val = val;
message->src = TOS_LOCAL_ADDRESS;
...
To send out the packet, we call a SEND_MSG command specifying the destination node address, the handler id on the destination and the source message buffer (the buffer, not the payload).
if (TOS_COMMAND(INT_TO_RFM_SUB_SEND_MSG)(TOS_BCAST_ADDR, AM_MSG(INT_READING), &VAR(data)))
This command is a split-phase request, like acquiring sensor data; it starts the process of sending a message and runs concurrently with the caller. Notice that the command may fail (like any other); this means that the messaging component didn't even accept the request. If it succeeds, it is working on the send.
TOS_BCAST_ADDR is a special address signifying that any node that hears the message will handle it. This is the native mode of radio operation. Alternatively, you can specify a particular node and all other that hear the message will discard it.
The handler that will process the message on the specfied recipient nodes is specified using AM_MSG(handler_name). The handler will be declared as TOS_MSG_EVENT(handler_name).
In this case AM_MSG(INT_READING) specifies the handler. Most of the time yu will find that messages are sent to similar conponents on other nodes, so it make sense to specify the handler on the sending side. This example is a little unusual as we will build a recipient application that is different. It will be register in a compatible fashion.
TinyOS message buffers follow a strict alternating
ownership protocol to avoid expensive memory management while allowing
concurrent operation. If the message layer accepts the send command,
it owns the send buffer and the requesting component should not touch it
until the send is complete, as indicated by a send_done event. The
requesting component may use any method to track the status of the buffer.
The only time pointers are carried across component
boundaries is pointers to TOS_MSGs.
Here we use a pending flag to keep track of the status of the buffer.
If the previous message is still being sent, we cannot touch the buffer,
so we drop this output operation. If the send buffer is available,
we can modify it and request it to be sent. If the send request fails,
we clear the flag, so we can try another one later. (Notice there
is no race condition in the use of the flag.)
char TOS_COMMAND(INT_TO_RFM_OUTPUT)(int val){
...
if (!VAR(pending)) {
VAR(pending) = 1;
message->val = val;
message->src = TOS_LOCAL_ADDRESS;
if (TOS_COMMAND(INT_TO_RFM_SUB_SEND_MSG)(TOS_BCAST_ADDR,
AM_MSG(INT_READING), &VAR(data))) {
return 1;
} else {
VAR(pending) = 0; /* request failed,
free buffer */
}
}
return 0;
}
In tos/shared/INT_TO_RFM.desc,
INT_TO_RFM is wired to tos/shared/GENERIC_COMM.comp,
which is the component providing the generic network stack. This
is a much more substantial use of hierarchical description files.
If we look inside the joint description, tos/shared/GENERIC_COMM.desc,
we see that it contains six additional components, from the high level
messaging component all the way down to modulation of individual bits on
the radio link. Many of these components exist in numerous versions
for different application contexts, but this is nicely abstracted.
The TOS_CALL_COMMAND(INT_TO_RFM_SUB_SEND_MSG)(TOS_BCAST_ADDR,
AM_MSG(INT_READING), &VAR(data))
directly links to
char COMM_SEND_MSG(short addr, char type, TOS_Msg* data) in GENERIC_COMM.
The COMM_SEND_MSG command returns a value to indicate whether it accepts the request or not. Return value of 1 indicates request is accepted while a return value of 0 indicates rejection.
The destination handler is wired to a specific Active Message handler. The AM component will dispatch incoming message to many different components that send messages within an application.
Here the INT_TO_RFM:INT_READING message handler is wired to GENERIC_COMM:GENERIC_COMM_MSG_HANDLER_4. GENERIC_COMM provides the mapping from handler name to handler identifier (here 4). An application specific register of handler types is maintained in the release.
When transmission of a message is completed, GENERIC_COMM will signal a COMM_SEND_DONE event to all components that are registered with the Active Message component. A pointer to the buffer sent is provided as an argument to the event.
Here this is wired to INT_TO_RFM_SUB_MSG_SEND_DONE event handler. It checks if the buffer is its own and, if so, clears the pending flag and signals COUNTER:COUNTER_OUTPUT_COMPLETE.
char TOS_EVENT(INT_TO_RFM_SUB_MSG_SEND_DONE)(TOS_MsgPtr sentBuffer){
if (VAR(pending) && sentBuffer == &VAR(data))
{
VAR(pending) = 0;
TOS_SIGNAL_EVENT(INT_TO_RFM_COMPLETE)(1);
return 1;
}
return 0;
}
This event is broadcast to all potential senders because other components may have failed to send a previous message and be waiting for the message layer to become available. We don't want them to spin consuming precious resources, so the notification is provided.
On the receiving side, the INT_READING msg event will be signaled.
In this case, we will use a different application to handle that.
include modules{
MAIN;
RFM_TO_LEDS;
};
The RFM_TO_LEDS compoent in tos/shared/RFM_TO_LEDS.desc uses GENERIC_COMM to receive messages,and INT_TO_LEDS as its output device.
include modules{
GENERIC_COMM;
INT_TO_LEDS;
};
In tos/shared/RFM_TO_LEDS.desc, wiring to the GENERIC_COMM for receiving messages, and INT_TO_LEDS for outputing the value on the LEDs are shown below
RFM_TO_LEDS:INT_READING GENERIC_COMM:GENERIC_COMM_MSG_HANDLER_4
RFM_TO_LEDS:RFM_TO_LEDS_LED_OUTPUT INT_TO_LEDS:INT_TO_LEDS_OUTPUT
Recall that INT_TO_RFM:INT_READING has an Active Message handler type that equals 4. RFM_TO_LEDS should also wire its INT_READING handler to have the same Active Message type equals 4.
Storage management of the receiving side is inherently dynamic. A message arrives and fills a buffer. The Active Message layer decodes the handler type and dispatches it. It gives the buffer to the application component when it does so, but the application must return a pointer to a free buffer upon completion.
All GENERIC_COMM handlers have the following interface that application handler for incoming messages:.
TOS_MsgPtr GENERIC_COMM_HANDLER_X(TOS_MsgPtr data);
where data is the TOS_Msg pointer pointing to the incoming message.
In RFM_TO_LEDS, this is the INT_READING
message handler:
/* Active Message handler
Pull out the data and send it to the output. */
TOS_MsgPtr TOS_MSG_EVENT(INT_READING)(TOS_MsgPtr msg){
int_to_led_msg* message = (int_to_led_msg*)msg->data;
TOS_CALL_COMMAND(RFM_TO_LEDS_LED_OUTPUT)(message->val);
return msg;
}
Observe that the event process the data within the packet and returns the buffer it was provided.
A clean way to extract the data from the payload is to use the same
int_to_led_msg
structure that we used in INT_TO_RFM. The integer value can
then be retrieved by the expression, message->val. The value
is then passed as an argument to the command RFM_TO_LEDS_LED_OUTPUT,
which displays the least significant 3 bits of the integer on the LEDs.
In this lab setting, you will want a unique group id. Edit apps/Makeinclude and change the line DEFAULT_LOCAL_GROUP = 0x7D to be the number on your programming board.
In addition, the header carries the destination node number (which may be BCAST_ADDR or the special UART address) and the handler id.
We have standardized our message size to be 36 bytes, where 4 bytes are allocated for the header (2 bytes for destination, 1 byte for type, and 1 byte for GROUP_ID), 30 bytes are allocated for application data payload, and 2 bytes are allocated for 16-bit CRC checksum. It is important to note that there are two addresses that have special purposes. TOS_BCAST_ADDR (0xffff) is used as a broadcast address while TOS_UART_ADDR (0x7e) is for messages destined to the serial port directly. Finally, TOS_LOCAL_ADDRESS is the address of the current node.
Compile and run both apps/cnt_to_leds_and_rfm.desc and apps/rfm_to_leds/rfm_to_leds.desc. When you turn on cnt_to_leds_and_rfm you should see the count displayed on the rfm_to_leds device. Congratulations you are doing wireless networking. Can you change this to provide a wireless sensor? (hint: SENS_TO_RFM).