|
|
Lesson 2: An Introduction to TinyScript
Last modified: Thu Aug 26 18:53:27 PDT 2004
|
This lesson introduces the TinyScript
language. Using the VM from Lesson 1, you write a
series of increasingly complex programs, culminating
in one that sends a temperature reading when
filtered light readings go over a threshold.
|
Introduction
|
In Lesson 1, you installed a few scripts on
Bombilla motes. The scripts were in TinyScript,
which is a simple BASIC-like language. TinyScript is
imperative and dynamically typed. Every VM has a set
of events that it handles. Each event has a handler,
a piece of code that runs when that event
occurs. TinyScript has basic control structures,
such as loops and conditionals. Part of customizing
a VM is to include functions to support the sets of
operations an application needs.
|
TinyScript Structure and Variables
|
A TinyScript program two parts: variable
declarations and statements. All variables that a
program uses must be declared before any
statements. For example, this is CntToLeds from
Lesson 1:
Variable Declarations |
|
private counter;
|
Statements |
|
counter = counter +1;
led(counter % 8);
|
TinyScript has three kinds of variables: private,
shared, and buffer. Private variables are
local to a handler; only the handler that declares
the variable can access it. For example, if two
handlers both have a private variable named
counter, each one has its own, independent
variable.
In constrast, shared variables are not
unique to handlers: this allows two handlers to
share data. If two handlers both have a shared
variable named counter, it is a single
variable that they share. If one handler increments
it, the other handler will see the change.
Shared and private variables are dynamically
typed. TinyScript has two basic types: integers and
sensor readings. Sensor readings are further divided
into all of the types of sensors (a magnetometer
reading is distinct from a light
reading). "Dynamically typed" means that a program
dynamically determines the type of a variable as it
runs, rather than than a compiler or programmer
stating it at compile-time. For example:
private val;
val = 1; ! val is an integer
val = light(); ! val is now a light reading
val = magX(); ! val is now a magnetometer reading (x-axis)
Dynamic typing means that a variable can have "no
type." A variable with no type acts as the identity
(e.g., zero). For example, in the following program
the variable counter has never been used
before:
private counter; ! no type: never been used before; first execution
counter = counter + 1; ! counter takes type integer, with value 1
Types limit what operations on a variable are
valid. Sensor readings are immutable. You cannot add
an integer to a light reading, a light reading to a
temperature reading, or even two light readings. If
you want to process sensorn readings, you must turn
them into integers with the int() function.
For example:
private total;
private count;
private val;
val = light(); ! legal: val is now a light reading
val = light() / 2; ! error: cannot divide light reading by 2
val = light() + magX(); ! error: cannot add light and magX
val = light() + light(); ! error: cannot add two light readings
val = int(light()); ! legal: val is now an integer
total = total + val; ! legal: total was an int
count = count + 1; ! legal: count was an int
val = total/count; ! legal: average of readings
Finally, buffer variables are vectors of a
fixed maximum size, and are always shared. Just like
private and shared scalars, buffers are typed; a
buffer can only contain values of a single type. A
buffer's contents and type can be cleared with the
bclear() function. If a buffer has no type,
it takes the type of the first value put into
it.
buffer bufOne;
buffer bufTwo;
bclear(bufOne); ! clear bufferOne
bufOne[0] = 5; ! Put 5 in index 0: bufOne has size one, type integer
bufOne[] = id(); ! Append mote ID to bufOne, now has size two
bufOne[4] = 41; ! Put 41 in index 4; bufOne has size five,
! buf[2] and buf[3] are zero
bufOne[5] = light(); ! error: buffer is type integer, not light
bufOne[5] = int(light); ! legal: integer
bufTwo = bufOne; ! make bufTwo a copy of bufOne
|
Simple Sensing
|
To sample sensor readings, your mote must have a
sensor board. By default, Bombilla compiles for the
standard mica sensor board (micasb), and provides
sensors for light and temperature. If you have that
sensor board, it should work fine. If you are using
a different sensor board -- the mica weather board
(micawb), for example -- then you may need to rebuild and recompile
Bombilla.
As CntToLeds in Lesson 1 showed, periodic
programs in Maté usually have two scripts:
the first is what to do periodically, the second
starts the periodic execution. Instead of displaying
a counter, as in CntToLeds, the following timer
script samples the light sensor (or other sensor, if
you have a board besides micasb).
Open the "timer0.txt" file you wrote in lesson
one, and modify it to be
private reading;
reading = light();
led(reading / 128);
This displays the top three bits of the sensor
reading. Inject this with Scripter as before:
java net.tinyos.script.Scripter -comm tossim-serial timer0 timer0.txt
If Timer0 is already running (e.g., you kept your
mote on from last lesson), then this script will
start executing immediately. If you need to start
Timer0, inject "once.txt" from the previous lesson,
which calls settimer0().
You should see this error message appear at the
console:
Error received:
Context: TIMER0
Cause: TYPE_CHECK
Capsule: TIMER0
Instruction: 5
The led() function takes a value, but light()
returns a light reading: when Maté tried to
execute led() the parameter failed a type
check. When the VM encounters a run-time error, it
halts execution of all contexts, flashes the LEDs,
and periodically broadcasts an error message to the
radio and UART: the Scripter tool read it from the
UART and displayed the error. Installing new code
clears the VM from the error state (it
reboots). Change "timer0.txt" to be:
private reading;
reading = int(light());
led(reading / 128);
Inject it with Scripter. The top three bits of
the light sensor will properly display on the mote
LEDs.
|
Event Handlers and the Scripter GUI
|
All of the scripts so far have been for the Once
or Timer0 events, and only used a small number of
functions. The Bombilla VM has a wider range of
event handlers that you can use. To see the full set
of options, run Scripter with the -gui
option:
java net.tinyos.script.Scripter -gui
You should see a window similar to this:
The Scripter GUI has three major parts. The left
panel has a selection for which event handler you
which to program. The Bombilla VM has six event
handlers. They are:
- Broadcast: Runs when the
mote receives a packet broadcast by another mote
with the bcast() function.
- Once: Runs once, when it installs. You
used this event to start Timer0 in earlier parts
of the tutorial.
- Reboot: Runs after the VM reboots
(every time new code for any handler
arrives).
- Timer0: Runs when timer0 fires. The
function settimer0() sets the firing
rate.
- Timer1: Runs when timer1 fires. The
function settimer1() sets the firing
rate.
- Trigger: Runs when another handler
calls the trigger() function.
Below the handler selection is a list of all
current shared variables and buffers, and below that
is a button for injecting code.
The center panel is where you write program
text. The GUI currently limits programs to 99 lines.
The right panel displays the set of functions a VM
provides. Below the list is a text area that
displays a brief description of a function. So far,
you've used the light, id,
settimer0, and led functions.
You can load text files into the program text
area with the File menu's Load
element.
Select the Timer0 handler, enter this code in the
program window and inject it. It toggles the red
mote LED when it runs:
led(25);
Select the Timer0 handler, enter this code into
the program window and inject it. It toggles the green
mote LED when it runs:
led(26);
Finally, inject a Once handler that starts the
two Timers at different rates:
settimer0(1);
settimer1(8);
You should see the red and green LEDs blinking at
different frequencies. The description of the
led function describes how to set, turn on,
turn off, and toggle LEDs. When you're done playing
with the LEDs, inject a Once handler that stops the
timers (calls the settimer functions with zero as a
parameter).
|
Sensor Sampling
|
For the rest of the tutorial, you can either use
the Scripter GUI or its command line tool
invocation. The parameters to the command line are:
java net.tinyos.script.Scripter <handler name> <file name>
Handler name is case-insensitive (Once is
the same as once), but file name is
case-sensitive.
TinyScript has standard language control
structures, like loops and conditionals. For
example, this program puts light readings into a
buffer until it is full, the clears the
buffer. bfull returns true if a buffer is
full, false otherwise. bsize returns how
many elements are in a buffer. bclear
clears a buffer, emptying it and making it have no
type. Inject it as the Timer0 handler:
buffer data;
data[] = light(); ! This appends the reading
if bfull(data) then
bclear(data);
end if
Start Timer0 with a Once handler that calls
settimer0(2) (5 Hz). Another way to fill
the buffer (by directly indexing):
buffer data;
data[bsize(data)] = light(); ! This puts the reading at the first free element
if bfull(data) then
bclear(data);
end if
To make sure the program is doing what you think
it is, include an LED indicator of whether it's
cleared the buffer:
buffer data;
data[] = light();
if bfull(data) then
bclear(data);
led(2); ! Turn on LED 1
else
led(1); ! Turn on LED 2
end if
You should see the geen LED on, but every two
seconds it will briefly turn off and the red will
turn on.
This next script is a little more complex: when
the buffer is full, it uses a loop to pull the
highest value out of the buffer and display its top
three bits on the LEDs :
buffer data;
private i;
private size;
private max;
private val;
data[] = light();
if bfull(data) then
size = bsize(data);
max = 0;
for i=0 until i >= size ! Scan through values
val = int(data[i]);
if (val > max) then
max = val; ! Find the max
end if
next i
led(max / 128); ! Display max on LEDs
bclear(data);
else
led(0); ! Turn off the LEDs
end if
There are two basic kinds of loops in TinyScript
(as in BASIC): unconditional and conditional. The
above loop is conditional: is loops until the
condition i >= size is true. Loops can
also be unconditional, where they loop for a finite
number of steps. These loops take the form
for i=0 to x
...
next i
Where x is a constant (it cannot be a
variable). If you want to increment until a
variable, then you should use a conditional loop as
the above handler does.
By default the initialized variable (i in
the above example) increments by one on each
execution. You can change this with the
step qualifier. For example, this loops
counter from 0 to 100 in increments of
ten:
for counter=0 to 100 step 10
...
next counter
The above filtering handler is relatively long
and cumbersome: it compiles to fifty-one
bytes. Bombilla provides functions to support this
sort of processing. The functions bsorta
and bsortd sort the elements of a buffer in
ascending and descending order respectively. An
simpler version of the above program is:
buffer data;
private val;
data[] = light();
if bfull(data) then
bsortd(data); ! Sort in descending order (highest is first)
led(int(data[0]) / 128); ! Display max on LEDs
bclear(data);
else
led(0); ! Turn off the LEDs
end if
|
Transmitting Readings Over the UART
|
So far, all of the programs have been using the
LEDs to display what they're doing. Bombilla also
has functions that support communication. For
example, the uart function takes a buffer
as a parameter and sends that buffer over the mote
UART. This Timer0 handler is similar to the
filtering one above, but sends the mote ID and
reading to the UART in addition to displaying the
reading on the LEDs.
buffer data;
buffer sendBuffer;
private val;
data[] = light();
if bfull(data) then
bsortd(data);
val = int(data[0]);
bclear(sendBuffer); ! Clear out the send buffer
sendBuffer[0] = id(); ! Buffer has 2 elements: mote ID and value
sendBuffer[1] = val;
led(val / 128); ! Display top 3 bits of value
uart(sendBuffer); ! Send the buffer to the UART
bclear(data);
else
led(0);
end if
The uart function sends a TinyOS packet
containing a Maté data buffer. When you
compile a VM, it builds a Java class representing
this packet, called BufferUARTMsg. The
Maté toolchain has a tool for reading these
packets. It needs to connect to the mote, so if you
are using the Scripter GUI (which keeps its
connection open), you have to use a SerialForwarder
so both the GUI and packet reader can listen to mote
packets. That is, if you are using the GUI you
should not use -comm serial. Start the
reader with:
java net.tinyos.script.VMBufferReader
VMBufferReader has an optional -comm
parameter; by default it connects to SerialForwarder
on the local machine. If the above script is
running, you should soon see output like this:
We're connected to avrmote
Received UART buffer of type VALUE, size 2
[0][948]
Received UART buffer of type VALUE, size 2
[0][882]
Received UART buffer of type VALUE, size 2
[0][979]
Received UART buffer of type VALUE, size 2
[0][861]
Received UART buffer of type VALUE, size 2
[0][1016]
The first number (zero in the above output) is
the mote ID; the second is the sensor reading.
If you want to build your own GUIs or Java tools
on top of Maté, you'll probably be dealing
with data buffers. The source code of VMBufferReader
should be a good place to start on how to do so.
Of
course transmitting to the UART is only sort-of
useful; Maté can also send buffers up an
ad-hoc collection tree. T
|
Conditional Sensing
|
The Trigger context allows a handler to request
an asynchronous operation. When a handler calls the
trigger function (which takes a buffer as a
parameter), it triggers the Trigger handler to run
(hence its name). The passed buffer is copied to the
Trigger context, so the caller can continue
executing. Meanwhile, the Trigger handler will start
running in parallel.
We can use this behavior to write a program that
has two parts. The first is a handler that
periodically runs and filters light readings. When
the filtered value goes over a threshold, the
handler triggers the Trigger handler. The Trigger
handler then samples the temperature sensor and
sends a packet to the UART with the reading. The
periodic handler is essentially a condition ("when
filtered light is above x"), and the Trigger handler
is the action to take when the condition is
two. Putting them in separate handlers allows you to
reprogram the two individually.
Stop Timer0 and Timer1 by sending a Once handler
that calls the settimer functions with a
period of zero:
settimer0(0);
settimer1(0);
Next, install the Trigger handler. This program
samples the temperature sensor and sends a buffer
with the reading to the UART:
buffer buf;
bclear(buf);
buf[0] = temp();
led(26); ! Blink green
uart(buf);
Install a Timer0 handler that filters light
readings with an exponentially weighted moving
average (EWMA), and when that goes over a threshold
calls trigger():
private average;
buffer empty;
average = average + int(light());
average = average / 2;
led(25); ! Blink red
if (average > 600) then
trigger(empty);
end if
Finally, start Timer0 firing at 5Hz with a Once
handler:
settimer0(2);
The mote will blink its red LED on each light
sample, and blink the green LED when it sends a
temperature reading. If it isn't triggering, then
you can lower the threshold in the Timer0 handler
from 600; hopefully this should help. Note that in
the above program Trigger sends a buffer of type
TEMP (temperature readings).
This concludes Lesson 2. In the next lesson,
you'll start using the radio.
< Previous Lesson |
Next Lesson > |
Top
|
Incorporating Other Sensor Boards
|
If you have sensorboards other than the standard
mica sensor board (micasb), then you need to rebuild
and recompile Bombilla to support your board. This
requires modifying the VM specification file. Lesson 4 has details on
doing this, but here we'll just change it in a very
limited fashion.
Maté currently supports the mica sensor
board (micasb) and the basic sensor board
(basicsb). We hope to support additional boards in
the future. Lesson 5
shows how Maté can be extended to support
additional boards. Making Bombilla use a different
sensor board requires changing the VM specification
file.
Open the Bombilla VM specification file
bombilla.vmsf in a text editor. It resides
in the samples subdirectory of
lib/VM The fifth line of the file says
<LOAD FILE="../sensorboards/micasb.vmsf">
Depending on what board you want, the line to:
basic sensor board (basicsb):
|
<LOAD FILE="../sensorboards/basicsb.vmsf">
|
telos onboard sensors:
|
<LOAD FILE="../sensorboards/telos.vmsf">
|
Basically, change the name of the vmsf file to
the sensorboard. This will automatically include
functions for all of the sensorboard's sensors. If
this means that you have functions for sensors that
are not populated on your board (e.g., you do not
have a magnetometer on your micasb), don't worry
about it. In the long term -- say, you were
deploying a Maté network -- you may want to
not include them, but for the sake of the tutorial
it's not an issue.
Once you have made this change, rebuild
Bombilla. Remember that this must be done from
the samples directory:
java net.tinyos.script.VMBuilder -nw bombilla.vmsf
Then, go to apps/Bombilla and recompile
the VM for your hardware.
These are the sensors the various sensor boards
provide:
micasb |
basicsb |
telos |
accelX |
light |
photoactive |
accelY |
temp |
totalsolar |
light |
|
humidity |
magX |
|
temperature |
magY |
|
mic |
|
temp |
|
Note that some Telos motes do not have their
sensors populated. This causes the
totalsolar and photoactive sensors
to return bogus values: they are simple ADC
captures. In contrast, the humidity and
temperature functions interact with digital
sensors over a bus; if they are not populated, the
VM will enter an error state.
|
|