From: prabal Date: Sat, 14 Apr 2007 07:16:23 +0000 (+0000) Subject: Update tutorials to bring into sync with code X-Git-Tag: tinyos/2.0.1~47 X-Git-Url: https://oss.titaniummirror.com/gitweb?a=commitdiff_plain;h=07a85b6e2a0a552c98aa93e77b8ac042bbad69b2;p=tinyos-2.x.git Update tutorials to bring into sync with code --- diff --git a/doc/html/tutorial/lesson7.html b/doc/html/tutorial/lesson7.html index eb58f152..d1fae350 100644 --- a/doc/html/tutorial/lesson7.html +++ b/doc/html/tutorial/lesson7.html @@ -7,7 +7,7 @@
Lesson 7: Permanent Data Storage
-
Last Modified: November 5, 2006
+
Last Modified: April 13, 2007

This lesson introduces permanent (non-volatile) data storage in TinyOS. Permanent storage allows a node to persist data even if power @@ -44,8 +44,9 @@ circular logs, and large objects. TinyOS 2.x also provides

Interfaces

Let's take a look at some of the interfaces that are in the -tos/interfaces directory to familiarize ourselves with -the general functionality of the storage system: +tos/interfaces directory and the types defined in the +tos/types to familiarize ourselves with the general +functionality of the storage system:

@@ -69,6 +70,9 @@ the general functionality of the storage system:

  • LogWrite +
  • +Storage.h + @@ -174,20 +178,131 @@ for more details.

    Storing Configuration Data

    -

    In Lesson 3, we -implemented a simple application called BlinkToRadio that used a -single timer, set to fire at a fixed rate, to toggle the LEDs. +

    This lesson shows how configuration data can be written to and read +from non-volatile storage. Configuration data typically exhibit some +subset of the following properties. They are limited in size, +ranging from a fews tens to a couple hundred bytes. Their values may +be non-uniform across nodes. Sometimes, their values are +unknown prior to deployment in the field and sometimes their +values are hardware-specific, rather than being tied to the +software running on a node. + +

    Because configuration data can be non-uniform across nodes or +unknown a priori, their values may be difficult to specify at +compile-time and since the data are sometimes hardware-specific, their +values must survive reprogramming, suggesting that encoding these +values in the program image is not the simplest approach. Storing +configuration data in volatile memory is also problematic since this +data would not survive a reset or power cycle. + +

    In summary, configuration data must persist through node resets, +power cycles, or reprogramming, and then be restored afterward. The +ability to persist and restore configuration data in this manner is +useful in many scenarios. + +

    + +

    + + +Now that we have discussed why one might use this type of +storage, let's see how to use it. We will implement a simple +demo application that illustrates how to use the Mount +and ConfigStorage abstractions. A timer period will be +read from flash, divided by two, and written back to flash. An LED is +toggled each time the timer fires. But, before diving into code, +let's discuss some high-level design considerations. + +

    +See tinyos-2.x/apps/tutorials/BlinkConfig/ for the accompanying code. -

    This lesson shows how parameters, like the timer period, can be -configured at runtime and persisted across node power cycles. The -ability to store configuration data is useful in many applications. -For example, it may be necessary to store a node's coordinates which -are only known after the node is deployed. Let's walk through the -steps to use the ConfigStorage abstraction: +

    + +Prior to its first usage, a volume does not contain any valid data. +So, our code should detect the first usage of a volume and take any +appropriate actions (e.g. preload it with default values). Similarly, +when the data layout of the volume changes (for example, if the +application requires new or different configuration variables), then +application code should detect this and take appropriate actions +(e.g. migrate the old data to the new layout or erase the volume and +reload the defaults). These requirements suggest that we should have +a way of keeping track of the volume version. We will use a version +number for this purpose (and will need to maintain a discipline of +updating the version number when the data layout changes +incompatibly). Our configuration struct might have the following +fields for the version number and blink period: + +

    +typedef struct config_t {
    +  uint16_t version;
    +  uint16_t period;
    +} config_t;
    +
    + +

    +

      @@ -222,8 +337,8 @@ ConfigStorageC component (e.g. BlinkConfigAppC.nc): module BlinkConfigC { uses { ... - interface Mount; interface ConfigStorage as Config; + interface Mount; ... } } @@ -240,25 +355,20 @@ implementation { components new ConfigStorageC(VOLUME_CONFIGTEST); ... - App.Mount -> ConfigStorageC.Mount; App.Config -> ConfigStorageC.ConfigStorage; + App.Mount -> ConfigStorageC.Mount; ... }
    1. Before the flash chip can be used, it must be mounted using the -two-phase mount/mountDone command. Here we show chaining how this -might be chained into the boot sequence: +two-phase mount/mountDone command. Here we show how this might be +chained into the boot sequence:
         event void Boot.booted() {
      -    call AMControl.start();
      -  }
      +    conf.period = DEFAULT_PERIOD;
       
      -  event void AMControl.startDone(error_t error) {
      -    if (error != SUCCESS) {
      -      call AMControl.start();
      -    }
           if (call Mount.mount() != SUCCESS) {
             // Handle failure
           }
      @@ -266,94 +376,269 @@ might be chained into the boot sequence:
       
    2. If the Mount.mount succeeds, then the Mount.mountDone -event will be signaled. In this case, we call the -ConfigStore.mount command from with the event handler: +event will be signaled. The following code shows how to check if the +volume is valid, and if it is, how to initiate a read from the volume +using the ConfigStore.read command. If the volume is +invalid, calling Config.commit will make it valid (this +call is also used to flush buffered data to flash much like the UNIX +fsync system call is supposed to flush buffered writes to disk):
         event void Mount.mountDone(error_t error) {
      -    if (error != SUCCESS) {
      -      // Handle failure
      +    if (error == SUCCESS) {
      +      if (call Config.valid() == TRUE) {
      +        if (call Config.read(CONFIG_ADDR, &conf, sizeof(conf)) != SUCCESS) {
      +          // Handle failure
      +        }
      +      }
      +      else {
      +        // Invalid volume.  Commit to make valid.
      +        call Leds.led1On();
      +        if (call Config.commit() == SUCCESS) {
      +          call Leds.led0On();
      +        }
      +        else {
      +          // Handle failure
      +        }
      +      }
           }
           else{
      -      call Config.write(CONFIG_ADDR, &period, sizeof(period));
      +      // Handle failure
           }
         }
       
      -
    3. Once mounted, the flash can be written: + +
    4. If the read is successful, then a Config.readDone +event will occur. In this case, we first check for a successful read, +and if successful, we then check the version number. If the version +number matches what we expected, we copy of the configuration data to +a local variable, and adjust its values. If there is a version +mismatch, we set the value of the configuration information to a +default value. Finally, we call the the Config.write +function:
      -  event void Mount.mountDone(error_t error) {
      -    if (error != SUCCESS) {
      -      // Handle failure
      +  event void Config.readDone(storage_addr_t addr, void* buf, 
      +    storage_len_t len, error_t err) __attribute__((noinline)) {
      +
      +    if (err == SUCCESS) {
      +      memcpy(&conf, buf, len);
      +      if (conf.version == CONFIG_VERSION) {
      +        conf.period = conf.period/2;
      +        conf.period = conf.period > MAX_PERIOD ? MAX_PERIOD : conf.period;
      +        conf.period = conf.period < MIN_PERIOD ? MAX_PERIOD : conf.period;
      +      }
      +      else {
      +        // Version mismatch. Restore default.
      +        call Leds.led1On();
      +        conf.version = CONFIG_VERSION;
      +        conf.period = DEFAULT_PERIOD;
      +      }
      +      call Leds.led0On();
      +      call Config.write(CONFIG_ADDR, &conf, sizeof(conf));
           }
      -    else{
      -      call Config.write(CONFIG_ADDR, &period, sizeof(period));
      +    else {
      +      // Handle failure.
           }
         }
      +
       
    5. Data is not necessarily "written" to flash when -ConfigStore.write is called. To ensure data is persisted -to flash, a ConfigStore.commit call is required: +ConfigStore.write is called and +Config.writeDone is signaled. To ensure data is +persisted to flash, a ConfigStore.commit call is +required:
         event void Config.writeDone(storage_addr_t addr, void *buf, 
      -    storage_len_t len, error_t result) {
      +    storage_len_t len, error_t err) {
           // Verify addr and len
       
      -    if (result == SUCCESS) {
      -      // Note success
      +    if (err == SUCCESS) {
      +      if (call Config.commit() != SUCCESS) {
      +        // Handle failure
      +      }
           }
           else {
             // Handle failure
           }
      -    if (call Config.commit() != SUCCESS) {
      -      // Handle failure
      -    }
         }
       
      -
    6. Finally, when the commit is complete, data can be read back from -the flash: +
    7. Finally, when the Config.commitDone event is +signaled, data has been durably written to flash and will survive a +node power cycle:
      -  event void Config.commitDone(error_t error) {
      -    if (call Config.read(CONFIG_ADDR, &period2, sizeof(period2)) != SUCCESS) {
      +  event void Config.commitDone(error_t err) {
      +    call Leds.led0Off();
      +    call Timer0.startPeriodic(conf.period);
      +    if (err == SUCCESS) {
             // Handle failure
           }
         }
      +
      - event void Config.readDone(storage_addr_t addr, void* buf, - storage_len_t len, error_t result) __attribute__((noinline)) { - memcpy(&period2, buf, len); +
    - if (period == period2) { - call Leds.led2On(); +

    Logging Data

    + +Reliable (atomic) logging of events and small data items is a common +application requirement. Logged data should not be lost if a system +crashes. Logs can be either linear (stop logging when the volume is +full) or circular (overwrite the least recently written data when the +volume is full). + +

    + +The TinyOS LogStorage abstraction supports these requirements. The log +is record based: each call to LogWrite.append (see below) creates a +new record. On failure (a crash or power cycle), the log only loses +whole records from the end of the log. Additionally, once a circular +log wraps around, log writes only lose whole records from the +beginning of the log. + +

    + +A demo application called PacketParrot shows how to use +the LogWrite and LogRead abstractions. A +node writes received packets to a circular log and retransmits the +logged packets (or at least the parts of the packets above the AM +layer) when power is cycled. + +

    + +See +tinyos-2.x/apps/tutorials/PacketParrot/ for the +accompanying code. + +

    + +The application logs packets it receives from the radio to flash. On +a subsequent power cycle, the application transmits any logged +packets, erases the log, and then continues to log packets again. The +red LED is on when the log is being erased. The blue (yellow) LED +turns on when a packet is received and turns off when a packet has +been logged successfully. The blue (yellow) LED remains on when +packets are being received but are not logged (because the log is +being erased). The green LED flickers rapidly after a power cycle +when logged packets are transmitted. + + +

      + +
    1. The first step when using the log is to decide what kind of data +you want to store in the log. In this case, we will declare a struct +of the type: + +
      +  typedef nx_struct logentry_t {
      +    nx_uint8_t len;
      +    message_t msg;
      +  } logentry_t;
      +
      + +
    2. Unlike Config storage, Log storage does not require the volume to +be explicitly mounted by the application. Instead, a simple read +suffices in which a buffer and the number of bytes to read are passed +to LogRead.read: + +
      +  event void AMControl.startDone(error_t err) {
      +    if (err == SUCCESS) {
      +      error_t e;
      +      do {
      +        e = call LogRead.read(&m_entry, sizeof(logentry_t));
      +      } while (e != SUCCESS);
      +    }
      +    else {
      +      call AMControl.start();
           }
      +  }
      +
      + +
    3. If the call to LogRead.read returns SUCCESS, then a +LogRead.readDone event will be signaled shortly +thereafter. When that happens, we check if the data that was returned +is the same length as what we expected. If it is, we use the data but +if not, we assume that either the log is empty or that we have lost +synchronization, so the log is erased: + +
       
      -    if (len == 2 && addr == CONFIG_ADDR) {
      +  event void LogRead.readDone(void* buf, storage_len_t len, error_t err) {
      +    if ( (len == sizeof(logentry_t)) && (buf == &m_entry) ) {
      +      call Send.send(&m_entry.msg, m_entry.len);
             call Leds.led1On();
           }
      +    else {
      +      error_t e;
      +      do {
      +        e = call LogWrite.erase();
      +      } while (e != SUCCESS);
      +      call Leds.led0On();
      +    }
         }
      +
       
      -
    +
  • The PacketParrot application stores packets received +over the radio to flash by first saving the message_t and +its length to a log_entry_t struct and then calling +LogWrite.append: -

    Logging Data

    +
    +  event message_t* Receive.receive(message_t* msg, void* payload, uint8_t len){
    +    call Leds.led2On();
    +    if (!m_busy) {
    +      m_busy = TRUE;
    +      m_entry.len = len;
    +      m_entry.msg = *msg;
    +      if (call LogWrite.append(&m_entry, sizeof(logentry_t)) != SUCCESS) {
    +        m_busy = FALSE;
    +      }
    +    }
    +    return msg;
    +  }
    +
    -See -tinyos-2.x/apps/tests/storage/Log/ for an example of -code that uses the Log storage abstraction. Log is generally used for -append-based data streams consisting of relatively small data items. -It provides atomicity guarantees for data writes. +
  • If the LogWrite.write returned SUCCESS, then a short +time later, a LogWrite.appendDone will be signaled. This +event returns the details of the write including the source buffer, +length of data written, whether any records were lost (if this is a +circular buffer) and any error code. If no errors occurred, then the +data was written to flash with atomicity, consistency, and durability +guarantees (and will survive node crashes and reboots): + +
    +  event void LogWrite.appendDone(void* buf, storage_len_t len, 
    +                                 bool recordsLost, error_t err) {
    +    m_busy = FALSE;
    +    call Leds.led2Off();
    +  }
    +
    +
    + +

    Storing Large Objects

    +Block storage is generally used for storing large objects that cannot +easily fit in RAM. Block is a low-level system interface that +requires care when using since it is essentially a write-once model of +storage. Rewriting requires an erase which is time-consuming, occurs +at large granularity (e.g. 256 B to 64 KB), and can only happen a +limited number of times (e.g. 10,000 to 100,000 times is typical). +The TinyOS network reprogramming system uses Block storage to store +program images. + +

    + See tinyos-2.x/apps/tests/storage/Block/ for an example -of code that uses the Block storage abstraction. Block is generally -used for storing large objects that cannot easily fit in RAM. +of code that uses the Block storage abstraction.

    Conclusions