TeenyLIME

TeenyLime in a Nutshell

TeenyLime targets a scenario in which all sensor network components participate in the computation without relying on an external base station. Applications exhibiting such behavior include those where actuators collect information from neighboring sensors and perform some action based on the value returned.

This need for coordination among peer devices mimics the coordination supported by Lime, thus it is natural to adopt the transiently shared tuple space as the core abstraction of TeenyLime. The coordination operations in TeenyLime are essentially those of Lime, including operations to insert, read, remove, and react-to tuples. TeenyLime tuple spaces are physically located on the devices themselves but unlike Lime they are shared only with one-hop neighbors. Because each device has a different set of one-hop neighbors, the shared tuple space view is different for each device. This is fundamentally in contrast to Lime in which the view of the transiently shared tuple space is composed of the tuple spaces of all connected hosts, and connectivity is assumed transitive. In general, limiting the scope of operations to one hop is natural for many WSN applications that need access to nearby information. For example, a fire extinguisher can make a local decision to activate based only on readings from several sensors in its vicinity, and inform other nearby extinguishers after it activates. Activation can be effected by installing a reaction on neighboring sensors for temperature readings. When sufficiently many high readings are received, the extinguisher should be activated. Notification to the other extinguishers in the area can be handled by outputting a "notification" tuple to their tuple space. The information about which devices are currently directly reachable is stored in a special tuple space called TeenyLime system, providing a single unified abstraction for representing both the application and system context. This is similar to the LimeSystem tuple space of Lime that reports which hosts are currently sharing tuple spaces, although the TeenyLime system supports weaker semantics.

TeenyLIME API

TeenyLime is written in nesC on top of TinyOS. A dedicated nesC interface (illustrated next) is used (in the TinyOS sense) by the application to access the transiently shared tuple space composed of the local tuple space and that of the one-hop neighbors. Each nesC command requires a target, a specification of the tuple space repositories in the federation over which the operation should execute. Possible values restrict the scope of the operation to the local tuple space (indicated with TOS_LOCAL_ADDRESS), the tuple space hosted by a specific one-hop neighbor (indicated with the address of the device), the union of all tuple spaces hosted by one-hop neighbors (TOS_BCAST_ADDR).

TupleSpace.nc
interface TupleSpace {   // Standard operations   command   TLOpId_t out(bool reliable, TLTarget_t t, tuple *t);   command   TLOpId_t rd(bool reliable, TLTarget_t t, tuple *templ);   command   TLOpId_t in(bool reliable, TLTarget_t t, tuple *templ);   // Reliable group operations   command   TLOpId_t rdg(bool reliable,TLTarget_t t, tuple *templ);   command   TLOpId_t ing(bool reliable, TLTarget_t t, tuple *templ);   // Managing reactions   command   TLOpId_t addReaction(bool reliable, TLTarget_t t, tuple *templ);   command   TLOpId_t removeReaction(TLOpId_t operationId);   // Request to reify a capability tuple   event   result_t reifyCapabilityTuple(tuple* ct);   // Returning tuples   event   result_t tupleReady(TLOpId_t operationId, tuple *t, uint8_t n); }
TeenyLIME API.

Concurrency

In TeenyLIME, all read operations are split-phase: first the operation is issued, then the tupleReady event is signaled when the operation completes. The return parameter for each operation is an identifier, or a special constant (TL_OP_FAIL) in case of error. The identifier and the data tuple(s) form the contents of the tupleReady event, allowing the application to associate the data with its earlier request. If multiple tuples are returned, the n parameter indicates how many.

Analogously, two asynchronous commands are offered to install or remove reactions, taking a pattern as the parameter. When matching data is present, the tupleReady event is signaled, returning the tuple that triggered the reaction along with the corresponding operation identifier.

Reliable Read/Write Operations

Typical WSN applications that focus on collecting sensor data are inherently state-less, as the core task is that of communicating sensor readings to a given collection point. Conversely, applications composed of tasks that affect the environment often require stateful coordination mechanisms, e.g., using current conditions (state) to act collaboratively. This poses more stringent requirements on the consistency of state, and consequently on the reliability of operations on the tuple space. To address both scenarios, the commands to perform read or write operations can be issued as either unreliable or reliable, using a flag. The former operations do not provide any guarantee on their successful completion, thus yielding a lightweight form of communication that is suited for state-less applications. Instead, reliable operations offer stronger guarantees at the price of higher resource consumption, allowing them to be used for coordination purposes, e.g., to implement control loops based on the representation of the current state of the environment.

Freshness

Sensor data is inherently time sensitive, a dimension that is even more important when actions must be taken based on the values themselves. For instance, a temperature reading might require different responses depending on how long ago it was gathered. To address time in general, TeenyLime divides time into epochs of constant length, and timestamps every outputted tuple with the current epoch value. Two helper functions are offered to the application developers (defined in TupleSpace.h): setFreshness(pattern,freshness) and getFreshness(tuple). The first customizes a pattern to impose the additional constraint to match tuples no more than freshness epochs old (if a pattern does not specify freshness, it matches any tuple regardless of its timestamp). Conversely, getFreshness(tuple) returns the number of epochs that elapsed since the tuple was created. An additional setExpireIn(tuple,epochs) function is also provided to set an expiration time for the tuple once output to the tuple space.

Range Matching

While the standard matching semantics of Lime suffice for basic coordination, some WSN applications require additional expressive power. For example, a fire extinguisher application only needs to obtain temperature readings above a safety threshold. However, using the standard matching semantics based on exact values, we must issue a reaction over the neighborhood with a pattern matching any temperature reading and filter the results when they arrive. This causes unnecessary communication when data is discarded upon arrival, therefore TeenyLime extends patterns to support range matching. For example, the reaction can be issued stating that the field representing the temperature value must be greater than a given value.

The following fragment of code illustrates how this is achieved with TeenyLime. In addition to the usual formal and actual tuple fields, patterns can also contain customized fields whose matching semantics account for (bounded or unbounded) intervals. The following code specifies that a tuple matches the pattern when the first field is equal to the constant TEMPERATURE, and the second field is an integer value above 30.

   tuple t; TLOpId_t reactionId;
   void addTemperatureReaction() {
     t = newTuple(2, // The fields of this pattern
             actualField_uint8_t(TEMPERATURE),
             greaterField(TYPE_UINT16_T, 30);
     reactionId = call TupleSpace.addReaction(
                       FALSE, TL_NEIGHBORHOOD, &t);
     if (reactionId == TL_OP_FAIL)
       dbg(DBG_ERR, "addReaction operation failed");
     else
       pendingReact[pendingReactLength++] = reactionId;
  }
Example use of range matching.

This mechanism is easily extensible because the matching semantics is decoupled w.r.t. distribution mechanisms. A developer needing alternate matching semantics must only define two functions: one creating a customized field for patterns, and one defining the conditions for an actual value to match the customized field.

Capability Tuples

Consider a small modification of the aforementioned scenario, namely a device needing to read a single temperature value from a nearby device. For the pattern to find a match, at least one tuple must be present in the tuple space of the neighboring nodes at the time the rd(p) operation is issued. Therefore, because a device cannot predict when a rd(p) operation will be issued, any sensor that can produce a temperature reading is forced to periodically take fresh readings, and proactively output the values in the local tuple space even if no device is currently interested in them. This clearly constitutes a waste of resources.

To manage this problem, TeenyLime developers are given the ability to output capability tuples, as outlined in the next code example, indicating that a device has the capability to produce data of a given pattern. From the point of the view of the application performing a query nothing changes: a matching tuple containing a temperature value is returned by raising a tupleReady event. Behind the scenes, however, the processing occurring at the device hosting the capability tuple is different from the normal one. A capability tuple enjoys the same matching semantics as a normal tuple, but is not returned directly as a result. Instead, it essentially works as a placeholder for the real data. A positive match triggers the event reifyCapabilityTuple, which reports the matched capability tuple to the application running on the device hosting it. The application can then perform the operation associated with it (e.g., reading the current temperature), build a tuple on the fly, and output it to the tuple space. TeenyLime takes care of returning the tuple to the querying node as the result of the read operation.

   tuple t; TLOpId_t outOpId;
   void outputCapabilityTemp() {
     t = newCapabilityTuple(2, // The fields of this tuple
                 actualField_uint8_t(TEMPERATURE),
                 formalField(TYPE_UINT16_T));
     outOpId = call TupleSpace.out(FALSE, TL_LOCAL, &t);
     if (outOpId == OP_FAIL)
       dbg(DBG_ERR, "?out? operation failed"); 
   }
Example use of capability tuples.

Our original concept for capability tuples was to act as placeholders for real data, providing a mechanism to reduce the number of sensor readings to those strictly needed to answer queries. However, the concept naturally generalizes yielding a powerful abstraction. Consider that with a capability tuple a device triggers the execution of a set of operations on a neighboring device merely by issuing a query matching the pattern of the capability tuple. This set of operations can be more general than simply taking a sensor reading. For example, a programmer could define a capability tuple representing its ability to average some sensor readings taken over a given time period. When a rd(p) operation is issued with a pattern matching this average capability tuple, the corresponding function is triggered and the result returned. Additionally, actual values in the pattern can be used as parameters, further customizing execution, e.g., to specify the desired time period in this example.

To exploit this general capability function feature, the developer simply defines a suitable capability tuple and the function to be called when the corresponding reifyCapabilityTuple event is raised. The result of the function is placed in a tuple, stored in the tuple space, and returned to the calling device.

TeenyLIME System

As previously mentioned, the system context is represented and accessible in TeenyLime in the same way as application data, i.e., as tuples. In particular, TeenyLime automatically provides access to the current neighbor set by storing a NeighborTuple for each device in range. The content of this tuple is defined by the application developer, allowing customization to include information such as the current location and/or the remaining energy. To keep the information up to date, the system periodically signals a reifyNeighborTuple event trough the TeenyLIMESystem interface, which the application should handle by returning a pointer to a new version of its NeighborTuple. If the event is ignored, TeenyLime keeps the previous tuple.

TeenyLIME