Tải bản đầy đủ
4 Multi-hop networking: collection, dissemination, and base stations

4 Multi-hop networking: collection, dissemination, and base stations

Tải bản đầy đủ

96

Applications

how the base station mote sets itself up as the root of the collection tree to report theft
settings to the PC, and how it disseminates new settings received from the PC. Chapter 7
presents the PC-side of the mote connection.

6.4.1

Collection
Sending a message via a collection tree is very similar to using AM, except that messages
do not have a destination address (the tree root is the implicit destination). Thus collection
trees use the Send interface, which is identical to AMSend except for the lack of an addr
parameter to the send command:

i n t e r f a c e Send {
c o m m a n d e r r o r _ t send ( m e s s a g e _ t * msg , u i n t 8 _ t len );
event void s e n d D o n e ( m e s s a g e _ t * msg , e r r o r _ t e r r o r );
c o m m a n d e r r o r _ t c a n c e l ( m e s s a g e _ t * msg );
c o m m a n d u i n t 8 _ t m a x P a y l o a d L e n g t h ();
c o m m a n d void * g e t P a y l o a d ( m e s s a g e _ t * msg , u i n t 8 _ t len );
}

Listing 6.18 The Send interface

As a result, the code in MovingC for reporting a theft over a collection tree is nearly
identical to that from Section 6.3.1:

uses i n t e r f a c e Send as T h e f t ;
...
m e s s a g e _ t r e p o r t M s g ; // The t h e f t r e p o r t m e s s a g e b u f f e r
bool s e n d i n g ;
// Don ’t try and send while a send is in p r o g r e s s
void r e p o r t T h e f t () {
t h e f t _ t * p a y l o a d = call T h e f t . g e t P a y l o a d (& reportMsg , s i z e o f ( t h e f t _ t ));
if ( p a y l o a d && ! s e n d i n g )
{ // We can send if we ’ re
idle and the p a y l o a d fits
payload - > who = T O S _ N O D E _ I D ; // R e p o r t t h a t * we * are b e i n g s t o l e n !
// And send the r e p o r t to the r o o t
if ( call Theft . send (& reportMsg , s i z e o f ( t h e f t _ t )) == S U C C E S S )
s e n d i n g = TRUE ;
}
}
event void T h e f t . s e n d D o n e ( m e s s a g e _ t * msg , e r r o r _ t e r r o r ) {
s e n d i n g = F A L S E ; // Our send c o m p l e t e d
}

Listing 6.19 Anti-Theft: reporting theft over a collection tree

6.4 Networking: collection, dissemination

6.4.2

97

Dissemination
The dissemination service is accessed by an interface parameterized by the type of value
being disseminated:

interface DisseminationValue {
c o m m a n d c o n s t t * get ();
c o m m a n d void set ( c o n s t t * );
event void c h a n g e d ();
}

Listing 6.20 DisseminationValue interface

The dissemination values are sent across the network, so the type passed to
DisseminationValue should be a platform-independent type (Section 3.5.3). In our case,
we reuse the settings_t type we defined in Section 6.3.2, which contains new acceleration
variance and check intervals. The code in MovingC to receive and handle new settings
is simple:

uses i n t e r f a c e D i s s e m i n a t i o n V a l u e < s e t t i n g s _ t > as S e t t i n g s ;
...
/* New s e t t i n g s received , u p d a t e our l o c a l c o p y */
event void S e t t i n g s . c h a n g e d () {
c o n s t s e t t i n g s _ t * n e w S e t t i n g s = call S e t t i n g s . get ();
accelVariance = newSettings -> accelVariance ;
call T h e f t T i m e r . s t a r t P e r i o d i c ( n e w S e t t i n g s - > a c c e l I n t e r v a l );
}

Listing 6.21 Anti-Theft: settings via a dissemination tree

6.4.3

Wiring collection and dissemination
Like AM itself, the collection and dissemination services must be explicitly started, this
time via the non-split-phase StdControl interface:

interface StdControl {
c o m m a n d e r r o r _ t s t a r t ();
c o m m a n d e r r o r _ t stop ();
}

Listing 6.22 The StdControl interface

Furthermore, as they are built over AM, the radio must be started first. As a result, the
AntiTheft boot sequence is now slightly more complex (as earlier, we don’t check error

98

Applications

codes as there is no obvious recovery step):
uses i n t e r f a c e S p l i t C o n t r o l as C o m m C o n t r o l ;
uses i n t e r f a c e S t d C o n t r o l as C o l l e c t i o n C o n t r o l ;
uses i n t e r f a c e S t d C o n t r o l as D i s s e m i n a t i o n C o n t r o l ;
...
event void B o o t . b o o t e d () {
call C o m m C o n t r o l . s t a r t ();
}

event void C o m m C o n t r o l . s t a r t D o n e ( e r r o r _ t ok ) {
// S t a r t multi - hop r o u t i n g and d i s s e m i n a t i o n
call C o l l e c t i o n C o n t r o l . s ta r t ();
call D i s s e m i n a t i o n C o n t r o l . s t a r t ();
// S t a r t c h e c k s o n c e c o m m u n i c a t i o n s t a c k is r e a d y
call T h e f t T i m e r . s t a r t P e r i o d i c ( A C C E L _ I N T E R V A L );
}

AM types identify different kinds of messages in a mote network. In a similar fashion,
collection and dissemination use 8-bit identifiers to allow for multiple independent
collection trees and for multiple values to be disseminated. As with AM, these
identifiers are specified as arguments to the CollectionSenderC and DisseminatorC
generic components that provide access to collection and dissemination:
enum { C O L _ T H E F T = 54 , D I S _ T H E F T = 55 };
...
components ActiveMessageC , DisseminationC , CollectionC ;
M o v i n g C . C o m m C o n t r o l -> A c t i v e M e s s a g e C ;
M o v i n g C . C o l l e c t i o n C o n t r o l -> C o l l e c t i o n C ;
M o v i n g C . D i s s e m i n a t i o n C o n t r o l -> D i s s e m i n a t i o n C ;

/* I n s t a n t i a t e and wire our c o l l e c t i o n s e r v i c e for t h e f t a l e r t s */
c o m p o n e n t s new C o l l e c t i o n S e n d e r C ( C O L _ T H E F T ) as T h e f t S e n d e r ;
M o v i n g C . T h e f t -> T h e f t S e n d e r ;

/* I n s t a n t i a t e and wire our d i s s e m i n a t i o n s e r v i c e for t h e f t s e t t i n g s */
c o m p o n e n t s new D i s s e m i n a t o r C ( s e t t i n g s _ t , D I S _ T H E F T );
M o v i n g C . S e t t i n g s -> D i s s e m i n a t o r C ;

6.4.4

Base station for collection and dissemination
For collection and dissemination to be useful, something must consume the messages sent
up the collection tree, and produce new values to disseminate. In the case of AntiTheft,
we assume a PC with a serial connection to a base station mote displays theft reports
and allows the theft detection settings to be changed. Here we just show how the base
station mote is setup to forward tree collection theft reports and disseminate theft settings;
Chapter 7 presents the libraries and utilities that are used to write PC applications that
communicate with mote networks.

6.4 Networking: collection, dissemination

99

The base station for AntiTheft is a separate nesC program. In other applications, base
stations and regular motes run the same nesC code but are distinguished using some other
means (e.g. many applications specify that a mote with identifier 0 is a base station).
A base station mote communicates with the PC via AM over the serial port, using
SerialActiveMessageC. The mote and the PC exchange settings (PCSettings) and theft
reports (PCTheft):
configuration AntiTheftRootAppC { }
implementation {
components AntiTheftRootC ;

components SerialActiveMessageC ,
new S e r i a l A M R e c e i v e r C ( A M _ S E T T I N G S ) as P C S e t t i n g s ,
new S e r i a l A M S e n d e r C ( A M _ T H E F T ) as P C T h e f t ;

A n t i T h e f t R o o t C . S e r i a l C o n t r o l -> S e r i a l A c t i v e M e s s a g e C ;
A n t i T h e f t R o o t C . R S e t t i n g s -> P C S e t t i n g s ;
A n t i T h e f t R o o t C . S T h e f t -> P C T h e f t ;
...
}

When the base station receives new settings, it simply calls the change command in
the DisseminationUpdate interface:

interface DisseminationUpdate {
c o m m a n d void c h a n g e ( t * n e w V a l );
}

Listing 6.23 The DisseminationUpdate interface

The call to change automatically triggers the dissemination process:

module AntiTheftRootC {
uses i n t e r f a c e D i s s e m i n a t i o n U p d a t e < s e t t i n g s _ t > as U S e t t i n g s ;
uses i n t e r f a c e R e c e i v e as R S e t t i n g s ;
...
/* When we r e c e i v e new s e t t i n g s f r o m the s e r i a l port , we d i s s e m i n a t e
them by c a l l i n g the c h a n g e c o m m a n d */
e vent m e s s a g e _ t * R S e t t i n g s . r e c e i v e ( m e s s a g e _ t * msg , void * payload , u i n t 8 _ t len )
{
if ( len == s i z e o f ( s e t t i n g s _ t ))
call U S e t t i n g s . c h a n g e (( s e t t i n g s _ t *) p a y l o a d );
r e t u r n msg ;
}

Listing 6.24 AntiTheft base station code: disseminating settings

100

Applications

To receive theft reports, the base station mote must mark itself as the root of the
collection tree using the RootControl interface:

interface RootControl {
c o m m a n d e r r o r _ t s e t R o o t ();
c o m m a n d e r r o r _ t u n s e t R o o t ();
c o m m a n d b o o l i s R o o t ();
}

Listing 6.25 The RootControl interface

Once a mote is the root of the collection tree, it receives the messages sent up the tree
via a regular Receive interface. In the case of the AntiTheft base station mote, it simply
forwards the payload of these messages (a theft_t value) to the serial port via its STheft
interface:

module
uses
uses
uses
...

AntiTheftRootC {
interface RootControl ;
i n t e r f a c e R e c e i v e as R T h e f t ;
i n t e r f a c e A M S e n d as S T h e f t ;

event void C o m m C o n t r o l . s t a r t D o n e ( e r r o r _ t e r r o r ) {
...
// Set o u r s e l v e s as the root of the c o l l e c t i o n tree
call R o o t C o n t r o l . s e t R o o t ();
}
message_t fwdMsg ;
bool f w d B u s y ;
/* When we ( as root of the c o l l e c t i o n tree ) r e c e i v e a new t h e f t alert ,
we f o r w a r d it to the PC via the s e r i a l p o r t */
e vent m e s s a g e _ t * R T h e f t . r e c e i v e ( m e s s a g e _ t * msg , void * payload , u i n t 8 _ t len )
{
if ( len == s i z e o f ( t h e f t _ t ) && ! f w d B u s y )
{
/* Copy p a y l o a d from c o l l e c t i o n s y s t e m to our s e r i a l m e s s a g e b u f f e r
( f w d T h e f t ) , then send our s e r i a l m e s s a g e */
t h e f t _ t * f w d T h e f t = call S T h e f t . g e t P a y l o a d (& fwdMsg , s i z e o f ( t h e f t _ t ));
if ( f w d T h e f t != NULL ) {
* f w d T h e f t = *( t h e f t _ t *) p a y l o a d ;
if ( call S T h e f t . s e n d ( T O S _ B C A S T _ A D D R , & fwdMsg , s i z e o f * f w d T h e f t )== S U C C E S S )
f w d B u s y = TRUE ;
}
}
r e t u r n msg ;
}

6.5 Storage

101

fwdBusy = FALSE ;
}
}

Listing 6.26 AntiTheft base station code: reporting thefts

The base station application must wire the serial port (already shown), and the
collection and dissemination interfaces used in the code above. Reception of messages
from a tree is specified by indexing a parameterized interface with the tree collection
identifier; parameterized interfaces are presented in detail in Section 8.3. The resulting
collection and dissemination wiring is:

configuration AntiTheftRootAppC { }
implementation {
... boot , s e r i a l and r a d i o w i r i n g ...
c o m p o n e n t s D i s s e m i n a t i o n C , new D i s s e m i n a t o r C ( s e t t i n g s _ t , D I S _ T H E F T );
A n t i T h e f t R o o t C . D i s s e m i n a t i o n C o n t r o l -> D i s s e m i n a t i o n C ;
A n t i T h e f t R o o t C . U S e t t i n g s -> D i s s e m i n a t o r C ;
components CollectionC ;
A n t i T h e f t R o o t C . C o l l e c t i o n C o n t r o l -> C o l l e c t i o n C ;
A n t i T h e f t R o o t C . R o o t C o n t r o l -> C o l l e c t i o n C ;
A n t i T h e f t R o o t C . R T h e f t -> C o l l e c t i o n C . R e c e i v e [ C O L _ T H E F T ];
}

Listing 6.27 AntiTheft base station wiring

6.5

Storage
Many motes include some amount of flash-based non-volatile storage, e.g. 512 kB on the
micaz. TinyOS divides this non-volatile storage into volumes. A volume is a contiguous
region of storage with a certain format that can be accessed with an associated interface.
TinyOS defines three basic storage abstractions: Log, Block, and Config. Log is for
append-only writes and streaming reads, Block is for random-access reads and write,
and Config is for small items of configuration data. Log and Config have the advantage
that their more limited interface allows for atomic operations: when a write to a Log or
Config volume completes, it is guaranteed to be written. In contrast, the Block interface
has a separate commit operation.
TinyOS uses this abstraction-on-a-volume approach rather than a more traditional
filing system for two main reasons. First, it is simpler, reducing code and RAM
requirements for applications using permanent storage. Second, sensor networks are
normally dedicated to a single application, which should have a reasonable knowledge

102

Applications

configtest
0

4608

logtest
131072

393216

524288

Figure 6.3 Sample volume specification and resulting flash layout for a micaz mote.

of its storage requirements. Thus, the full generality of a filing system is typically not
required.

6.5.1

Volumes
The division of a mote’s flash chip into volumes is specified at compile-time, by a
simple XML configuration file. Flash chips have different sizes, and different rules
on how they can be divided, so the volume specification is necessarily chip-specific.
By convention, the volume configuration for chip C is found in a file named
volumes-C .xml. For instance, Figure 6.3 shows a volumes-at45db.xml file
specifying two volumes for the Atmel AT45DB chip found on the micaz (see TEP
103 [4] for a specification of the volume configuration format). The first volume is
named LOGTEST, starts at an offset of 128kB and is 256 kB long. The second is named
CONFIGTEST and is 4.5kB with no offset specified: some offset will be picked at
compile-time.
A storage abstraction is mapped to a specific volume by instantiating the component
implementing the abstraction (LogStorageC, BlockStorageC, and ConfigStorageC) with
the volume identifier (prefixed with VOLUME_) as argument, e.g.:

generic configuration ConfigStorageC ( volume_id_t volid ) {
provides interface Mount ;
provides interface ConfigStorage ;
} ...
c o m p o n e n t s new C o n f i g S t o r a g e C ( V O L U M E _ C O N F I G T E S T ) as M y C o n f i g u r a t i o n ;

Listing 6.28 ConfigStorageC signature

A volume can be associated with at most one instance of one storage abstraction. A
storage abstraction instance has to be associated with a volume so that the underlying
code can generate an absolute offset into the chip from a relative offset within a
volume. For instance, address 16k on volume LOGTEST is address 144 k on the
AT45DB.

6.5 Storage

6.5.2

103

Configuration data
As it currently stands, anti-theft motes lose their settings when they are switched off. We
can fix this by storing the settings in a small configuration volume:
< volume_table >
< v o l u m e n a m e = " A T _ S E T T I N G S " size = " 512 " / >


configuration AntiTheftAppC { }
implementation {
...
c o m p o n e n t s new C o n f i g S t o r a g e C ( V O L U M E _ A T _ S E T T I N G S ) as A t S e t t i n g s ;
M o v i n g C . M o u n t -> A t S e t t i n g s ;
M o v i n g C . C o n f i g S t o r a g e -> A t S e t t i n g s ;
}

Configuration volumes must be mounted before use, using the split-phase Mount
interface:

interface Mount {
c o m m a n d e r r o r _ t m o u n t ();
event void m o u n t D o n e ( e r r o r _ t e r r o r );
}

Listing 6.29 Mount interface for storage volumes

Once mounted, the volume is accessed using the ConfigStorage interface:

interface ConfigStorage {
c o m m a n d e r r o r _ t read ( s t o r a g e _ a d d r _ t addr , void * buf , s t o r a g e _ l e n _ t len );
event void r e a d D o n e ( s t o r a g e _ a d d r _ t addr , void * buf , s t o r a g e _ l e n _ t len ,
e r r o r _ t e r r o r );
c o m m a n d e r r o r _ t w r i t e ( s t o r a g e _ a d d r _ t addr , void * buf , s t o r a g e _ l e n _ t len );
event void w r i t e D o n e ( s t o r a g e _ a d d r _ t addr , void * buf , s t o r a g e _ l e n _ t len ,
e r r o r _ t e r r o r );
c o m m a n d e r r o r _ t c o m m i t ();
event void c o m m i t D o n e ( e r r o r _ t e r r o r );
c o m m a n d s t o r a g e _ l e n _ t g e t S i z e ();
c o m m a n d bool valid ();
}

Listing 6.30 ConfigStorage interface

ConfigStorage volumes are somewhat unusual in that they effectively keep two copies
of their contents: the contents as of the last commit and the new, as-yet-to-be-committed

104

Applications

0

10
ff

ff

23

23

11

42

88

99

88

99

54

ab

d2

aa

ff

ff

pre-commit contents

write to bytes 2,3

54

ab

d2

aa

ff

ff

post-commit contents
10

0
Figure 6.4 ConfigStorage: writes and commits.

contents. The purpose here is to guarantee that configuration data is not lost: if a
mote crashes during configuration updates, the data last committed is still present and
uncorrupted. Reads always read the last-committed data, while writes update the new
copy – you cannot see the data that has been written until after a successful commit.
However, writes are still updates: if a configuration volume contains 10 bytes, and bytes
2 and 3 are updated with a write and the volume is then committed, the new contents
are bytes 0–1 and 4–9 from before the write, and bytes 2–3 from the write, as shown in
Figure 6.4. Because configuration volumes must store multiple copies of data to provide
reliability, the actual available size is less than the space reserved for them in the volumes
file. The actually available size is returned by the getSize command.
While configuration volumes aim to prevent data loss, there are still two cases where
they may not contain valid data: when the volume is first created,3 or if the flash contents
get corrupted by some external physical process (hopefully unlikely). The valid command
can be used to find out whether a configuration volume contains valid data.
Putting all this together, adapting MovingC to have permanent settings is not too hard:
the configuration volume is mounted and read at boot time, and new settings are saved
when they are received. The following simplified excerpts show the basic boot-time
logic, with error-checking removed for simplicity:
uses i n t e r f a c e M o u n t ;
uses i n t e r f a c e C o n f i g S t o r a g e ;
...
settings_t settings ;
event void B o o t . b o o t e d () {
s e t t i n g s . a c c e l V a r i a n c e = A C C E L _ V A R I A N C E ; // d e f a u l t s e t t i n g s
settings . accelInterval = ACCEL_INTERVAL ;
call Mount . mo u n t ();
}

3 This really means: when this division of the flash is first used on this particular mote – there is no actual

“format”-like operation.

6.5 Storage

105

event void Mount . m o u n t D o n e ( e r r o r _ t ok ) {
if ( call C o n f i g S t o r a g e . v a l i d ())
call C o n f i g S t o r a g e . read (0 , & settings , s i z e o f s e t t i n g s );
else
call C o m m C o n t r o l . s t a r t ();
}

event void C o n f i g S t o r a g e . r e a d D o n e ( s t o r a g e _ a d d r _ t addr , void * buf ,
s t o r a g e _ l e n _ t len , e r r o r _ t e r r o r ) {
call C o m m C o n t r o l . s t a r t ();
}

Listing 6.31 Anti-Theft: reading settings at boot time

The current settings are simply saved as a module-level settings_t variable, which
is read if the volume is valid and left with default values if not. Updating the settings
simply involves calling write and commit (again with error-checking removed):

event void S e t t i n g s . c h a n g e d () {
s e t t i n g s = * call S e t t i n g s . get ();
call C o n f i g S t o r a g e . write (0 , & settings , s i z e o f s e t t i n g s );
}
event void C o n f i g S t o r a g e . w r i t e D o n e ( s t o r a g e _ a d d r _ t addr , void * buf ,
s t o r a g e _ l e n _ t len , e r r o r _ t e r r o r ){
call C o n f i g S t o r a g e . c o m m i t ();
}
event void C o n f i g S t o r a g e . c o m m i t D o n e ( e r r o r _ t e r r o r ) {
call T h e f t T i m e r . s t a r t P e r i o d i c ( s e t t i n g s . a c c e l I n t e r v a l );
}

Listing 6.32 Anti-Theft: saving configuration data

6.5.3

Block and Log storage
Block and Log storage are two abstractions for storing large amounts of data. Log is
intended for logging: it provides reliability (each write is a separate transaction), at the
cost of limited random access: writes are append-only, reads can only seek to recorded
positions. Block is a lower-level abstraction which allows random reads and writes, but
provides no reliability guarantees. Also, Block only allows any given byte to be written
once between two whole-volume erases. Block is often used to store large items, such
as programs for Deluge, the network reprogramming system.
In this section, we will use Block and Log to build FlashSampler, a two-level sampling
application. The system will periodically collect 32kB samples of an accelerometer,

106

Applications

storing the data in a Block storage volume. It will then log a summary of this sample to
a circular log. As a result, the system will at all times have the most recent measurement
with full fidelity, and some number of older measurements with reduced fidelity.
For the micaz with the same mts300 sensor board used for the anti-theft application,
the following volumes file specifies FlashSampler’s log and block volumes – 64 kB for
the latest sample (SAMPLES) and the rest for the long-term log (SAMPLELOG):
< volume_table >
< v o l u m e n a m e = " S A M P L E S " size = " 65536 " / >
< volume name =" SAMPLELOG " size =" 458752 "/>


Like the other storage abstractions, block storage is accessed by instantiating the
BlockStorageC component with the volume identifier and using its BlockRead and
BlockWrite interfaces:

generic configuration BlockStorageC ( volume_id_t volid ) {
provides interface BlockWrite ;
provides interface BlockRead ;
}

Listing 6.33 BlockStorageC signature

To sample to flash, we use the the BlockWrite interface:

interface BlockWrite {
c o m m a n d e r r o r _ t w r i t e ( s t o r a g e _ a d d r _ t addr , void * buf , s t o r a g e _ l e n _ t len );
event void w r i t e D o n e ( s t o r a g e _ a d d r _ t addr , void * buf , s t o r a g e _ l e n _ t len ,
e r r o r _ t e r r o r );
c o m m a n d e r r o r _ t e r a s e ();
event void e r a s e D o n e ( e r r o r _ t e r r o r );
c o m m a n d e r r o r _ t sync ();
event void s y n c D o n e ( e r r o r _ t e r r o r );
}

Listing 6.34 The BlockWrite interface

All commands are split-phase: erase erases the flash before the first use, write writes
some bytes, and sync ensures that all outstanding writes are physically present on the
flash.
FlashSampler uses BlockWrite and the ReadStream sampling interface we saw earlier
to simultaneously sample and save results to the flash. For this to work, it needs to
use two buffers: while one is being sampled, the other is written to the flash. The