Michael Demmer and Philip Levis
Experimenting with and testing sensor networks is hard. TOSSIM, a TinyOS simulator, allows users to run and test algorithms, protocols, and applications in a controlled, reproducible environment. However, by itself a TOSSIM simulation is static. Instead of modeling behaviors such as motion or changing sensor readings, TOSSIM provides a socket-based command API for other programs to do so. On one hand, this keeps TOSSIM simple and efficient; on the other, it puts the burden of writing complex real-world models on the user.
One solution to this problem is TinyViz, a GUI that communicates with TOSSIM over the socket API. With TinyViz, users can interact with a simulation through a GUI panel, by dragging motes and setting options. These actions can be difficult to reproduce exactly (e.g., dragging a mote). Additionally, TinyViz can (as its name suggests) visualize what goes on in the network. Users can write TinyViz ``plugins'' in Java to extend the GUI's functionality and issue commands to TOSSIM. However, because users must precompile their plugins, this only allows limited interactivity.
Tython (or, Tinython) complements TinyViz's vizualization by adding a scripting interace to TOSSIM. Users can interact with a running simulation through TinyViz, a Tython console, or both simultaneously. Tython is based on Jython, a Java implementation of the Python language. In addition to being a complete scripting language (with plenty of documentation, literature, tutorials, etc.), Jython makes it very easy to import and use Java classes within Python. This allows users to access the entire TinyOS Java tool chain, including packet sources, MIG-generated messages, and TinyViz.
This document explains how to get started with Tython, and explains the library functions that can be used to access TOSSIM or TinyViz. It does not contain an in-depth Python tutorial: many of those can be found at www.python.org.
TinyViz and Tython sit on top of SimDriver, a java application that manages interactions with TOSSIM. To get started, just run the application:
java net.tinyos.sim.SimDriver
The Jython environment may take a little while to come up the first time; it scans all of the TinyOS and system jar files, but caches the information for later invocations. (Passing -Dpython.cachedir=[dir] to the application will select a directory besides the current one for the cached files.)
After booting, Tython will give you a script prompt:
>>>
SimDriver can be invoked with a variety of options. For example, -gui has it start a GUI interface (TinyViz). For backwards compatibility, TinyViz arguments such as -run and -autorun are still supported. The tython console will start by default, though it can be disabled with the -noconsole option. For example,
# java net.tinyos.sim.SimDriver -noconsole -gui -run main.exe 20
Will start TinyViz without a console and start a TOSSIM simulation (in this case, main.exe) of 20 motes. This is identical to starting TinyViz in previous releases.
The -h flag option will proint the full set of SimDriver command line options. For TOSSIM options, refer to the TOSSIM reference manual.
Note: when SimDriver invokes TOSSIM, it always enables the TOSSIM lossy radio model.
As in TinyViz, one easy way to get SimDriver to communicate with the simulation is to first run TOSSIM with the -gui option, then run SimDriver. At startup, SimDriver will indicate that it has successfully connected to TOSSIM:
Starting SimDriver... Initializing simulator objects... Loading simulator plugins... status: Connecting to simulator... status: Connection to simulator established SimComm: TossimInitEvent received... initializing system. Welcome to Tython. Type 'quit' to exit. ESC on a line by itself will pause/resume the simulator. >>>
At this point, the simulation is running and you can start interacting with the running simulation using the Tython API.
You can also use the scripting interface to SimDriver to run TOSSIM for you. This is useful to allow you to write a script that runs a number of simulations.
First off, just run SimDriver. At the Tython prompt, run the following:
>>> from simcore import * >>> sim.exec("build/pc/main.exe", 10)
The exec function takes as arguments the path to the executable and the number of motes to run, much as the -run command line option does. This will execute a new TOSSIM process and connect to it.
You can also pass additional arguments to TOSSIM as follows:
>>> from simcore import * >>> sim.exec("build/pc/main.exe", 10, "-l=1.0 -b=1")
The Tython interpreter runs concurrently with the TOSSIM simulation itself, hence you can interact with the running simulation. However, to add some more control to interactions, you can also pause the simulation to examine state, control several settings at once, etc.
The sim object has function interfaces to pause, resume, and probe the paused state:
>>> from simcore import * >>> sim.resume() >>> sim.pause() >>> if (sim.isPaused()): ... sim.resume()As a shortcut, hitting the escape key and hitting return will toggle the pause state of the simulation. If the simulator is running, it will pause it; if paused, it will resume it.
In addition to a basic Jython environment, Tython has several objects that provide functions to interact with TOSSIM. The previous sections showed some of this, specifically how the sim object can be used to execute, pause and resume a simulation.
Notice that in the previous examples, the simcore module was imported into the running environment. This module is, appropriately enough, the core interface that is used to interact with the Tython environment. This module is in fact not Python code at all, instead, a single instance of each class of the Java net.tinyos.sim.script.reflect package is created and is bound into the simcore module.
Some example commands are shown in the following table. The javadoc for the reflect package lists the complete interface.
The previous examples of Tython scripting assumed the use of the console. While appropriate for interactive use, this is cumbersome for repeated actions.
The builtin execfile command can be used to source a file and execute the contents as Tython commands:
>>> execfile("myscript.py")
This will read and execute the contents of the specified file. As an alternative, passing the command line argument -script to Tython will cause it to automatically source and execute the named script file. For example:
java net.tinyos.sim.SimDriver -script "myscript.py"
Jython's Java support allows a user to basically use it as a Java scripting language. It also provides the basic features of the Python environment, including builtin functions and data structures.
As mentioned previously, the simcore module provides the basic access to the simulator interactions. The builtin Python dir() function is a useful hook to see the set of functions an object provides. First, type the name of the object:
>>> from simcore import * >>> sim net.tinyos.sim.script.reflect.Sim@5f3a5f53
This tells you that the sim object is actually an instance of the net.tinyos.sim.script.reflect.Sim Java class. To see the variables and functions a Sim object provides, type dir(Sim).
>>> dir(Sim) ['__driver', 'argv', 'dumpDBG', 'exec', 'exit', 'getTossimTime', 'getWorldHeight', 'getWorldWidth', 'isPaused', 'pause', 'paused', 'reset', 'resume', 'setSimDelay', 'simDelay', 'stop', 'stopDBGDump', 'tossimTime', 'worldHeight', 'worldWidth']
dir() can be used on both Java and Python objects. For example, dir(Sim) returns a Python list of strings, with each element being a method or attribute of the Sim class. So, dir(dir(Sim)) lists the functions a Python list object provides:
>>> dir(dir(sim)) ['append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
Note that dir()'s semantics are slightly different for Python and Java. One can call dir() on a Python object to learn its functions, but for Java, one must call it on the class. Specifically:
>>> sim net.tinyos.sim.script.reflect.Sim@5f3a5f53 >>> dir(sim) [] >>> dir(Sim) ['__driver', 'argv', 'dumpDBG', 'exec', 'exit', 'getTossimTime', 'getWorldHeight', 'getWorldWidth', 'isPaused', 'pause', 'paused', 'reset', 'resume', 'setSimDelay', 'simDelay', 'stop', 'stopDBGDump', 'tossimTime', 'worldHeight', 'worldWidth'] >>> m = dir(Sim) >>> dir(m) ['append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
Calling dir() with no parameters lists the set of variables, functions, and modules currently bound in the Jython environment:
>>> dir() ['Commands', 'Interp', 'Location', 'Mote', 'Motes', 'PacketType', 'Packets', 'Radio', 'Sim', 'SimBindings', 'SimReflect', '__doc__', '__driver', '__name__', 'comm', 'interp', 'location', 'motes', 'packets', 'radio', 'sim', 'simcore', 'sys']
Finally, calling dir() on a module lists the set of objects and functions exposed by that module.
>>> import simcore >>> dir(simcore) ['Commands', 'Interp', 'Motes', 'Packets', 'Radio', 'Sim', '__doc__', '__name__', 'comm', 'interp', 'motes', 'packets', 'radio', 'sim']
Jython allows you to import Java classes with Python's import syntax. For example, to import java.util.Enumeration, or the entire java.util package:
>>> from java.util import Enumeration >>> from java.util import *
Python is a dynamically typed language. Therefore, variables do not have type specifiers. While instantiating a StringBuffer in Java looks like this:
StringBuffer buffer = new StringBuffer();
in Jython it looks like this,
buffer = StringBuffer()
Note, however, that Jython does not automatically import java.lang.*.
By importing Java classes, one can access the TinyOS Java toolchain:
from net.tinyos.message import * from net.tinyos.packet import * moteIF = MoteIF() msg = BaseTOSMsg() msg.set_addr(0) msg.set_type(5) moteIF.send(0, msg)
The above code will send a message of AM type 5 to mote 0 over the default MoteIF interface.
Access to the toolchain includes GUI-based programs. For example, one can instatiate a SerialForwarder:
from net.tinyos.sf import * s = SerialForwarder([])
Note, however, that some classes (SerialForwader's GUI among them) expect to be stand-alone applications. This means that when you tell them to quit, with the quit button, for example, they may cause the entire Jython environment to quit.
Given that all Java objects are accessible through the python interface, in many cases there is redundancy between the two languages, and there is more than one interface available for a given programming task.
Being dynamically typed, the Jython environment loses many of the correctness benefits of the Java compilation process. For objects such as strings, lists, and maps, it's often easier to use Python's primitives instead of Java's, as they are syntactically built into the language. For example, compare the use of a Python map and a Java hashtable:
# Java from java.util import * h = new Hashtable() h.put("Seed", getPacket()) print(h.get("Seed")) # Python h = {} h["Seed"] = getPacket() print h["Seed"]
Similarly, one can use either Java or Python to write a file:
# Java from java.io import * file = FileWriter("output.txt") file.write(packets[0]) # Python file = open("output.txt", "w") file.write(packets[0])
We've found that, for data collection and processing, the Python idioms are generally easier to use than Java. Among other things, its data structure primitives end up being a lot simpler and less error prone. However as mentioned previously, Java allows you to interact with all of the existing structure a TinyOS distribution provides, such as packet sources, message class generation, and interaction with TinyViz.
Much of the power of the Tython environment comes from the synergy between the two languages. Specifically, the use of Java to implement internal functionality, while presenting a Python interface for scripts.
The packets variable in the simcore module is an example of this. This data structure allows scripts to access all of the packets that motes have transmitted. The variable is a dictionary of lists, keyed by mote ID. Each list contains all of the packets a given mote has sent, in temporal order (the first packet is the first element of the list).
print packets[3]
will print all of the packets mote 3 has sent.
Each element of a given list is a Java instance of net.tinyos.sim.event.RadioMsgSentEvent. This class is used because it stores not only the message sent, but also metadata, such as time. The time represents when the packet send completed, not when it began.
RadioMsgSentEvent has three basic accessor functions: getMessage(), which returns a net.tinyos.message.TOSMsg, getMoteID(), which returns the sender's ID, and getTime(), which returns the send time.
packets is an immutable object; unlike normal dictionaries, a script cannot assign to it. If a script needs to transform the packet dictionary, it can make a copy with packets.copy(), which is a normal Python dictionary and can be modified accordingly.
In addition to normal dictionary operations, packets has an extra function to support TinyOS messages, addPacketType(Message message). This function can create packet type specific reflections of packets. For example,
from net.tinyos.message import IntMsg packets.addPacketType(IntMsg())
will create a new variable in the Tython environment, IntMsgs, which contains all of the messages in packets that are of type IntMsg, as defined by active message type. This variable, IntMsgs, can be indexed and accessed just as packets is.
Note, however, that each element in the packets lists contains a TOSMsg; this means that in the above example, IntMsgs also has TOSMsgs; you cannot easily manipulate the elements as if they were actually instances of IntMsg, by accessing their message type specific fields, etc.
In order to transform the IntMsgs into a dictionary of net.tinyos.message.IntMsg, you must call IntMsgs.downCast(). For example:
from net.tinyos.msg import IntMsg packets.addPacketType(IntMsg()) # Get a dictionary of IntMsgs, instead of TOSMsgs myMsgs = IntMsgs.downCast() # Print the value in the first IntMsg that mote 0 transmitted print myMsgs[0].pop().get_val()
Using a downCast() dictionary allows you to easily access message fields. However, it discards the additional metadata (such as transmit time) that a RadioMsgSentEvent contains.
The variable defined by addPacketType() will update as more motes send packets; it represents a filter on top of packets. The dictionary returned by downCast(), however, is static; it does not update as more motes send packets.
The core of SimDriver is an event bus. Java plugins can connect to this event bus, receiving TOSSIM events and sending TOSSIM commands; many of the Tython abstractions are built on top of SimDriver plugins. TinyViz, the visualization environment, also uses plugins, so users can write new visualizations. Correspondingly, there are two classes of SimDriver plugins: GUI and non-GUI. In addition, scripts can register event handlers as well, as described in a later section.
Examples of GUI plugins include TinyViz's radio quality visualization, its communication visualization, and the basic plugin that draws motes on the screen. Examples of non-GUI plugins include the radio model plugin and the packet logger plugin. GUI plugins are only loadable through TinyViz, while non-GUI plugins are accessible through Tython, and are used to actuate TOSSIM in response to network changes. SimDriver maintains a list of mote objects, which store state pertinent to the scripting environment, such as position and LED values.
For example, calling mote.moveTo() modifies that mote's position. This can change radio connectivity. The radio model notices the event that indicates a Mote's position has changed, and then calculates the appropriate bit error rates based on new distances. The plugin will then automatically communicate the new link qualities to TOSSIM, which is internally unaware of mote position.
Tython and TinyViz provide two position-based radio models: disc and empirical. The first models the network using "perfect disc" connectivity, at three different disc radii ("disc10", "disc100", and "disc1000"). Connectivity is perfect in that links are either error-free or not present, depending on whether the target is in or outside of the disc. Although links are perfect, collisions can still occur, which will corrupt a packet.
The second model, "empirical", uses an error distribution based on empirical data. Essentially, for any particular distance there is a distribution of possible bit error rates: two links of identical length can have different rates. These bit errors can cause a packet to be corrupted so that a receiver does not recover it properly. Links are directed: the error rate from A to B is distinct from the rate from B to A. Roughly speaking, motes closer than 16 units will have good connectivity, motes between 16 and 28 units will have highly variable and possibly asymmetric connectivity, and motes 30 units and higher away will have poor connectivity.
These position models boil down to a simple function: given a distance between two motes, produce a bit error rate. For disc-based models, this is a simple cutoff, which results in symmetric and perfect links. For the empirical model, the function returns a sample from the distribution corresponding to the distance.
Scripts can also explicitly specify connectivity qualities with the radio object. setLossRate(src, dest, prob) sets the bit error rate from ID src to ID dest to be probability prob. comm.packetLossToBitError(prob) calculates bit error rates from a packet loss rate*.
It is important to keep in mind that there at any one time, there are actually two radio models for a given simulation. TOSSIM internally maintains a table of bit error rates and uses this table to determine if any corruption should be applied to a radio message. In addition, SimDriver's Radio Model Plugin also maintains a table of error rates, driven by the above-described models. The correlation of these two data structures is controllable by the script (and also by the TinyViz GUI).
The radio.setLossRate() function only affects the data structure within SimDriver. Calling radio.publishModel(). propagates the entire connectivity graph to TOSSIM, which is O(n2). Therefore, if a large number of links are updated, it's best to only publish the model once, after all of the updates.
To avoid this complexity and inefficiency, the Radio model also supports an auto-publish mode. When enabled, any changes to the connectivity graph within SimDriver are immediately propagated to TOSSIM. Note however, that auto publish is not enabled by default, and a script must enable it if desired:
radio.setAutoPublish(True)
In addition, as mentioned previously, the radio models will automatically recalculate connectivity rates when Mote objects reposition themselves in the virtual simulator space. Note that if motes are moving around the space often, then auto-publish may result in a large number of messages being sent to TOSSIM to update the model.
When using Tython with TinyViz, scripts can add annotations to mote visualizations. The setLabel() method of the mote class takes three parameters: a string, an X offset, and a Y offset. For example,
motes[2].setLabel("queue depth: " + depth, 10, 0)
will put a small label 10 pixels to the right of mote 2 in the TinyViz visualization panel, stating what its queue depth is (assuming the variable depth has been set to this value previously).
The previous sections described how to control and monitor TOSSIM through Tython. However, the examples given were fairly simple. By using some of Python's expressive power, one can write much more intricate and complex scripts.
Fundamentally, both TOSSIM and SimDriver are event-driven programs. Internal to TOSSIM is a master event queue that manages the various simulated motes. When SimDriver is connected to TOSSIM, events are exchanged over the network connection. In this way, the SimDriver core is structured as a set of event handlers for events both coming from TOSSIM and internally generated.
Many Tython primitives depend on being able to handle TOSSIM events which update the known state about the simulation. For example, every time a mote transmits a packet, TOSSIM sends an event to SimDriver. Internally, a plugin within SimDriver subscribes to packet event notifications and uses them to update the packets variable (discussed in a previous section).
Tython scripts can also register event handlers through the simcore.interp object. Event handlers must be functions that expect a single parameter, the event handled. Scripts register handlers with interp.addEventHandler(). The first parameter is the event handler function. The second, optional, parameter is the Java class type of the events that the function should handle (not an instance of the event class). Passing an event type will register the handler for only events of that type; omitting it will have it handle all event types. For example:
def print_handler(event): print event # Print all events interp.addEventHandler(print_handler) # Print only LED events from net.tinyos.sim.event import LedEvent interp.addEventHandler(print_handler, LedEvent)
The net.tinyos.sim.event package contains all of the events that TOSSIM generates (as well as a few which SimDriver uses internally). The TOSSIM events are:
|
Event handlers can be useful for waiting for a condition, or for logging information. For example, if you want to examine why a mote sends a specific (perhaps malformed) packet, you can register a handler for send events, and when the handler detects the packet, pause TOSSIM.
Most of the events described in the previous section are issued when a mote performs a certain action, or when the user makes an action in the TinyViz GUI. TOSSIM also provides a command for external programs to receive a callback in the future, the InterruptEvent. The method sim.interruptInFuture() command sends a command to TOSSIM which schedules an InterruptEvent at a designated time (in 4MHz simulator ticks). The command also takes an integer ID as a parameter, which is then used to distinguish between the InterruptEvents that fire. The sim.getInterruptID() API function can be used to get a unique id.
Here is an example use of these pause events, in this case where a user wants to print out mote state one minute into simulation:
import simcore from net.tinyos.sim.event import InterruptEvent id = simcore.interp.getInterruptID() def time_handler(event): global id if event.get_id() == id: print "Printing motes 1-20" for i in range(0, 20): print motes[i] simcore.interp.addEventHandler(time_handler, InterruptEvent) simcore.interp.interruptInFuture(60 * 4000000, id)
To help manage some of the event and id bookkeeping, the simutil package provides a simpler python class interface to accomplish the same task:
import simutil, simtime def time_handler(): print "Printing motes 1-20" for i in range(0, 20): print motes[i] simutil.CallIn(simtime.onemin, time_handler);
In this example, CallIn is a simple python class that manages the pause and event identifiers. Besides the constructor method that schedules the callback, it also exports a cancel() method, which allows a script to nullify a previously scheduled action.
In addition, note that the first parameter to simutil.CallIn uses the simtime module, specifically the onemin constant. simtime is a simple module that has predefined constants and conversion functions for manipulating 4MHz simulator clock ticks. Also, the first parameter to CallInis an offset from the current time, so the above call will occur one minute in the future. This is in contrast to both interp.interruptInFuture and the simple wrapper simutil.CallAt which both take a absolute time value.
The InterruptEvent interface is the primary means by which a script can schedule actions. Event handlers can also be made to reschedule themselves for periodic actions. For example, a simple change to the previous handler will cause the mote state to be printed every minute instead of only once:
def time_handler(event): global id if event.get_id() == id: print "Printing motes 1-20" for i in range(0, 20): print motes[i] # Reschedule to happen again in one minute simcore.interp.interruptInFuture(event.getTime() + simtime.onemin, id)
This can get the job done, but is clunky, verbose and difficult. Changing the period from once a minute to twice a minute requires a new function. In order to just print out mote state every thirty seconds, a user has to write a function, request a pause ID, register a handler, and schedule an event to go off.
The simutil package provides a convenience class, Periodic for exactly this reason:
from simutil import Periodic import simtime def time_handler(event): print "Printing motes 1-20" for i in range(0, 20): print motes[i] p = Periodic(simtime.onemin, time_handler)
Because Python functions are first class values, we can use closures to dynamically generate functions with the parameters a user needs. Thus telling Tython to print out a range of mote states periodically can be done with a single function call:
from simutil import Periodic import simtime def print_state(low_mote, high_mote, period): def time_handler(event): print "Printing motes ", low_mote, " to ", high_mote for i in range(low_mote, high_mote): print motes[i]; return Periodic(period, time_handler); p = print_state(3, 5, simtime.onemin);
In this example, print_state returns the newly allocated Periodic object instance, whose stop method can be used to stop the recurring action. Once print_state is called, the Tython environment will print out the state of motes low_mote to high_mote, every period ticks.
Closures can also be used for periodic commands to TOSSIM. For example, a test of a routing protocol may want to inject periodic beacons from a PC to a mote over the UART:
from simutil import * def uart_beacon(mote, period, msg): def sendMessage(event): comm.sendUARTMessage(mote, 0, msg) return Periodic(period, sendMessage)
Using closures as described in the previous section allows scripts to start periodic actions in a concise and simple manner. One use of this is to implement mote behaviors. For example, to make a mote move in a random walk, all one needs is a handler that moves a mote a small random step, then schedule this event to go off periodically.
One problem that emerges is conflicting behaviors. For example, one could call random_walk() to make a mote move randomly, and also call move_to() to make it move towards a specific qdestination. By registering two event handlers, the script has specified two conflicting movement behaviors, which will interleave and come into conflict.
A clean solution to solve this problem is through the use of a Python object. The object has functions for each of the different behaviors, and stores state on what behavior a mote is currently following. It also implements a singleton pattern to ensure that only one instance of hte object is created.
From an abbreviated example adapted from the simutil package:
# # MoteMover is a utility class that handles various movement patterns # for motes. # import simcore import simutil import simtime import math from net.tinyos.sim.event import InterruptEvent from java.util import Random # there should be only a single instance motemover = None; class MoteMover: def __init__(self): global motemover if (motemover != None): raise "Cannot instantiate MoteMover more than once" self.rand = Random(); handlers = {} rate = simtime.onesec # # Move the mote to the given x,y position, moving a distance of # 'step' each time. Calls the arrivedCallback when it gets there. # def moveTo(self, mote, step, x, y, arrivedCallback = None, rate = -1): moteID = mote.getID(); if rate == -1: rate = int(self.rate) if (self.handlers.has_key(moteID)): raise IndexError("Mote ID %d already on the move" % moteID) dx = x - mote.getXCoord(); dy = y - mote.getYCoord(); distance = mote.getDistance(x, y); nsteps = distance / step; xstep = dx / nsteps; ystep = dy / nsteps; def callback(pauseEvent): distance = mote.getDistance(x, y); if (distance < step): mote.moveTo(x, y); self.stop(mote); # clear handlers, cancel event, etc if (arrivedCallback != None): arrivedCallback(mote) else: mote.move(xstep, ystep); periodic = simutil.Periodic(rate, callback); self.handlers[moteID] = (periodic, 'move_to'); # # Move the given mote in a random walk pattern, moving a distance of # 'step' units on each time interval. # def randomWalk(self, mote, step, rate=-1): moteID = mote.getID(); if rate == -1: rate = self.rate if (self.handlers.has_key(moteID)): raise IndexError("Mote ID %d already on the move" % moteID) def callback(pauseEvent): x = self.rand.nextDouble() * step * 2.0 - step; y = self.rand.nextDouble() * step * 2.0 - step; simcore.motes[moteID].move(x, y) # print "random_walk: move mote %d by (%d, %d)" % (moteID, x, y); periodic = simutil.Periodic(rate, callback); self.handlers[moteID] = (periodic, 'random_walk')
In this example, the class variable handlers is used to store the notion of whether or not a particular mote is moving. Conflicting movement patterns are therefore detected and an error is reported. Additional movement patterns can be easily added to the object as new functions that follow the same pattern to avoid conflict.
One of Tython's most powerful features is the ability to request snapshots of variables from the running TOSSIM simulation. Specifically, the Mote object (accessible through the motes list), implements four functions (getBytes(), getLong(), getInt(), and getShort()) that allow you to request variable values from TOSSIM, returning them in the appropriate type.
The string parameter to each of these functions is the variable's C name. For nesC components, this takes the form of <component_name>$<variable_name> For example, this code fetches some variables from the TimerM component of mote 2:
tail = motes[2].getByte("TimerM$queue_tail") head = motes[2].getByte("TimerM$queue_head") print "head: ", head, "tail: ", tail
The getBytes() function can be used to fetch entire
structures. Currently, the variable parameter does not support
accessing structure fields, pointer traversals, or array elements.
Therefore, while getBytes("TimerM$mTimerList") will return
all of the timer structures as an array of bytes,
getBytes("TimerM$mTimerList[1]") and
getBytes("TimerM$mTimerList[1].ticks") do not work.
Tython is a new system that adds a powerful new tool to the sensor network developer's portfolio. Through both predefined scripts and interactive console sessions, Tython aids the tasks of developing, testing, and evaluating a new algorithm or application. The core architecture is extensible, allowing developers to write new python modules and SimDriver plugins to add new forms of interaction and manipulation.
The primary goal of Tython is to offer the sensor network developer a simulation environment with dynamic interactivity, enabling both unattended simulation experiments, as well as interactive debuggging and simulation control. The confluence of these two goals informs the major design decisions of our project, as well as the particular interfaces exposed by the Tython commands and classes.
In some senses, the first order design question relates to the value of a dynamic simulation environment. Indeed, TOSSIM alone is a valuable tool for aiding sensor network development, offering a scalable simulation of a network of sensor motes with a fairly realistic radio model and a rich debugging capability (gdb). Yet TOSSIM alone essentially just simulates tossing a set of motes into a field and letting them go, assuming a constant radio topology. On the other hand, the real world is a dynamic place; objects and motes can move, radio connectivitity changes, motes can fail. An important tool in the developer's toolbox, therefore, is the ability to simulate these dynamic interactions and thereby engineer a program that can cope with these situations.
Rather than integrating dynamically controlled interfaces to TOSSIM, the simulator instead implements a network protocol that allows interactive applications to connect to and control a running simulation. This separation allows the TOSSIM code base to remain insulated and relatively lean (maintaining performance capabilities), while more complicated calculations and interactivity can be implemented in a separate process.
TinyViz was a tool that enables developers to dynamically manipulate a simulation. The protocol between TOSSIM and TinyViz enables the GUI to introduce dynamics into a test application's execution. However, GUI elements do not address all the needs of a dynamic interaction. In general, a user's interaction with a GUI is non-deterministic, making repeated executions of a test case difficult if not impossible to reproduce. Furthermore, to run a set of experiments using manual manipulation of the simulation state is an extremely cumbersome task, limiting the developer to a simple cursory exploration of the potential interactivity parameter space.
The TinyViz plugin system is an available avenue to manipulate or visualize the parameters of an application. The plugin API allows a Java object to be loaded into the TinyViz GUI environment and to interact with the TOSSIM protocol. Through custom plugins, an application writer could fully express control over the dynamics of a particular experiment. In point of fact, much of the core Tython functionality is implemented using the TinyViz plugin system. However, writing custom plugins is an insufficient solution due not only to the cumbersome nature of writing experiments in a compiled language, but more importantly that it does not enable interactive code execution. In addition, being able to run an unattended simulation is a valuable convenience to developers.
Through a scripting environment, developers are able to control experiments through repeatable interactions. The Python and reflected Java commands/objects expose the key control elements of the simulation environment. These hooks can be used both in a controlled experiment framework and through console interaction with a running simulation. The interative console session is both useful for dynamic debugging and investigation of an experiment, but also as a prototyping arena for code that may become part of a longer experiment framework. The dynamic console functionality not only eases the burden of writing simulation scripts, but also enables interactive experimentation with the simulation itself. A developer can pause a simulation at a given time, use the variable resolution features to probe around (and potentially alter) the simulation state, then continue the simulation to observe the effects of the actions.
This document was generated using the LaTeX2HTML translator Version 2002 (1.62)
Copyright © 1993, 1994, 1995, 1996,
Nikos Drakos,
Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999,
Ross Moore,
Mathematics Department, Macquarie University, Sydney.
This document is based on an initial translation run by Michael Demmer on January 30, 2004 as a conversion from the previous version of the Tython manual. Since then, both formatting and content have been heavily modified.