[Tinyos-2-commits] CVS: tinyos-2.x/apps/tests/TestOscilloscopeLQI/java ColorCellEditor.java, NONE, 1.1 Data.java, NONE, 1.1 Graph.java, NONE, 1.1 Makefile, NONE, 1.1 Node.java, NONE, 1.1 Oscilloscope.java, NONE, 1.1 Window.java, NONE, 1.1 oscilloscope.jar, NONE, 1.1

Phil Levis scipio at users.sourceforge.net
Thu Feb 15 11:01:26 PST 2007


Update of /cvsroot/tinyos/tinyos-2.x/apps/tests/TestOscilloscopeLQI/java
In directory sc8-pr-cvs10.sourceforge.net:/tmp/cvs-serv20208/java

Added Files:
	ColorCellEditor.java Data.java Graph.java Makefile Node.java 
	Oscilloscope.java Window.java oscilloscope.jar 
Log Message:
The LQI multihop oscilloscope.


--- NEW FILE: ColorCellEditor.java ---
/*
 * Copyright (c) 2006 Intel Corporation
 * All rights reserved.
 *
 * This file is distributed under the terms in the attached INTEL-LICENSE     
 * file. If you do not find these files, copies can be found by writing to
 * Intel Research Berkeley, 2150 Shattuck Avenue, Suite 1300, Berkeley, CA, 
 * 94704.  Attention:  Intel License Inquiry.
 */

import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import java.awt.event.*;

/* Editor for table cells representing colors. Popup a color chooser. */
public class ColorCellEditor extends AbstractCellEditor
    implements TableCellEditor {
    private Color color;
    private JButton button;

    public ColorCellEditor(String title) {
	button = new JButton();
	final JColorChooser chooser = new JColorChooser();
	final JDialog dialog = JColorChooser.createDialog
	    (button, title, true, chooser,
	     new ActionListener() {
		 public void actionPerformed(ActionEvent e) {
		     color = chooser.getColor();
		 } },
	     null);

	button.setBorderPainted(false);
	button.addActionListener
	    (new ActionListener () {
		    public void actionPerformed(ActionEvent e) {
			button.setBackground(color);
			chooser.setColor(color);
			dialog.setVisible(true);
			fireEditingStopped();
		    } } );
	
    }

    public Object getCellEditorValue() { return color; }
    public Component getTableCellEditorComponent(JTable table,
                                                 Object value,
                                                 boolean isSelected,
                                                 int row,
                                                 int column) {
        color = (Color)value;
        return button;
    }
}


--- NEW FILE: Data.java ---
/*
 * Copyright (c) 2006 Intel Corporation
 * All rights reserved.
 *
 * This file is distributed under the terms in the attached INTEL-LICENSE     
 * file. If you do not find these files, copies can be found by writing to
 * Intel Research Berkeley, 2150 Shattuck Avenue, Suite 1300, Berkeley, CA, 
 * 94704.  Attention:  Intel License Inquiry.
 */

import java.util.*;

/* Hold all data received from motes */
class Data {
    /* The mote data is stored in a flat array indexed by a mote's identifier.
       A null value indicates no mote with that identifier. */
    private Node[] nodes = new Node[256];
    private Oscilloscope parent;

    Data(Oscilloscope parent) {
	this.parent = parent;
    }

    /* Data received from mote nodeId containing NREADINGS samples from
       messageId * NREADINGS onwards. Tell parent if this is a new node. */
    void update(int nodeId, int messageId, int readings[]) {
	if (nodeId >= nodes.length) {
	    int newLength = nodes.length * 2;
	    if (nodeId >= newLength)
		newLength = nodeId + 1;

	    Node newNodes[] = new Node[newLength];
	    System.arraycopy(nodes, 0, newNodes, 0, nodes.length);
	    nodes = newNodes;
	}
	Node node = nodes[nodeId];
	if (node == null) {
	    nodes[nodeId] = node = new Node(nodeId);
	    parent.newNode(nodeId);
	}
	node.update(messageId, readings);
    }

    /* Return value of sample x for mote nodeId, or -1 for missing data */
    int getData(int nodeId, int x) {
	if (nodeId >= nodes.length || nodes[nodeId] == null)
	    return -1;
	return nodes[nodeId].getData(x);
    }

    /* Return number of last known sample on mote nodeId. Returns 0 for
       unknown motes. */
    int maxX(int nodeId) {
	if (nodeId >= nodes.length || nodes[nodeId] == null)
	    return 0;
	return nodes[nodeId].maxX();
    }

    /* Return number of largest known sample on all motes (0 if there are no
       motes) */
    int maxX() {
	int max = 0;

	for (int i = 0; i < nodes.length; i++)
	    if (nodes[i] != null) {
		int nmax = nodes[i].maxX();

		if (nmax > max)
		    max = nmax;
	    }

	return max;
    }
}

--- NEW FILE: Graph.java ---
/*
 * Copyright (c) 2006 Intel Corporation
 * All rights reserved.
 *
 * This file is distributed under the terms in the attached INTEL-LICENSE     
 * file. If you do not find these files, copies can be found by writing to
 * Intel Research Berkeley, 2150 Shattuck Avenue, Suite 1300, Berkeley, CA, 
 * 94704.  Attention:  Intel License Inquiry.
 */

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.util.*;

/* Panel for drawing mote-data graphs */
class Graph extends JPanel
{
    final static int BORDER_LEFT = 40;
    final static int BORDER_RIGHT = 0;
    final static int BORDER_TOP = 10;
    final static int BORDER_BOTTOM = 10;

    final static int TICK_SPACING = 40;
    final static int MAX_TICKS = 16;
    final static int TICK_WIDTH = 10;

    final static int MIN_WIDTH = 50;

    int gx0, gx1, gy0, gy1; // graph bounds
    int scale = 2; // gx1 - gx0 == MIN_WIDTH << scale
    Window parent;

    /* Graph to screen coordinate conversion support */
    int height, width;
    double xscale, yscale;

    void updateConversion() {
	height = getHeight() - BORDER_TOP - BORDER_BOTTOM;
	width = getWidth() - BORDER_LEFT - BORDER_RIGHT;
	if (height < 1) 
	    height = 1;
	if (width < 1)
	    width = 1;
	xscale = (double)width / (gx1 - gx0 + 1);
	yscale = (double)height / (gy1 - gy0 + 1);
    }

    Graphics makeClip(Graphics g) {
	return g.create(BORDER_LEFT, BORDER_TOP, width, height);
    }

    // Note that these do not include the border offset!
    int screenX(int gx) {
	return (int)(xscale * (gx - gx0) + 0.5);
    }

    int screenY(int gy) {
	return (int)(height - yscale * (gy - gy0));
    }

    int graphX(int sx) {
	return (int)(sx / xscale + gx0 + 0.5);
    }

    Graph(Window parent) {
	this.parent = parent;
	gy0 = 0; gy1 = 0xffff;
	gx0 = 0; gx1 = MIN_WIDTH << scale;
    }

    void rightDrawString(Graphics2D g, String s, int x, int y) {
	TextLayout layout =
	    new TextLayout(s, parent.smallFont, g.getFontRenderContext());
	Rectangle2D bounds = layout.getBounds();
	layout.draw(g, x - (float)bounds.getWidth(), y + (float)bounds.getHeight() / 2);
    }

    protected void paintComponent(Graphics g) {
	Graphics2D g2d = (Graphics2D)g;

	/* Repaint. Synchronize on Oscilloscope to avoid data changing.
	   Simply clear panel, draw Y axis and all the mote graphs. */
	synchronized (parent.parent) {
	    updateConversion();
	    g2d.setColor(Color.BLACK);
	    g2d.fillRect(0, 0, getWidth(), getHeight());
	    drawYAxis(g2d);

	    Graphics clipped = makeClip(g2d);
	    int count = parent.moteListModel.size();
	    for (int i = 0; i < count; i++) {
		clipped.setColor(parent.moteListModel.getColor(i));
		drawGraph(clipped, parent.moteListModel.get(i));
	    }
	}
    }

    /* Draw the Y-axis */
    protected void drawYAxis(Graphics2D g) {
	int axis_x = BORDER_LEFT - 1;
	int height = getHeight() - BORDER_BOTTOM - BORDER_TOP;

	g.setColor(Color.WHITE);
	g.drawLine(axis_x, BORDER_TOP, axis_x, BORDER_TOP + height - 1);

	/* Draw a reasonable set of tick marks */
	int nTicks = height / TICK_SPACING;
	if (nTicks > MAX_TICKS)
	    nTicks = MAX_TICKS;

	int tickInterval = (gy1 - gy0 + 1) / nTicks;
	if (tickInterval == 0)
	    tickInterval = 1;

	/* Tick interval should be of the family A * 10^B,
	   where A = 1, 2 * or 5. We tend more to rounding A up, to reduce
	   rather than increase the number of ticks. */
	int B = (int)(Math.log(tickInterval) / Math.log(10));
	int A = (int)(tickInterval / Math.pow(10, B) + 0.5);
	if (A > 2) A = 5;
	else if (A > 5) A = 10;

	tickInterval = A * (int)Math.pow(10, B);

	/* Ticks are printed at multiples of tickInterval */
	int tick = ((gy0 + tickInterval - 1) / tickInterval) * tickInterval;
	while (tick <= gy1) {
	    int stick = screenY(tick) + BORDER_TOP;
	    rightDrawString(g, "" + tick, axis_x - TICK_WIDTH / 2 - 2, stick);
	    g.drawLine(axis_x - TICK_WIDTH / 2, stick,
		       axis_x - TICK_WIDTH / 2 + TICK_WIDTH, stick);
	    tick += tickInterval;
	}
	
    }

    /* Draw graph for mote nodeId */
    protected void drawGraph(Graphics g, int nodeId) {
	SingleGraph sg = new SingleGraph(g, nodeId);

	if (gx1 - gx0 >= width) // More points than pixels-iterate by pixel
	    for (int sx = 0; sx < width; sx++)
		sg.nextPoint(g, graphX(sx), sx);
	else // Less points than pixel-iterate by points
	    for (int gx = gx0; gx <= gx1; gx++)
		sg.nextPoint(g, gx, screenX(gx));
    }

    /* Inner class to simplify drawing a graph. Simplify initialise it, then
       feed it the X screen and graph coordinates, from left to right. */
    private class SingleGraph {
	int lastsx, lastsy, nodeId;

	/* Start drawing the graph mote id */
	SingleGraph(Graphics g, int id) {
	    nodeId = id;
	    lastsx = -1;
	    lastsy = -1;
	}

	/* Next point in mote's graph is at x value gx, screen coordinate sx */
	void nextPoint(Graphics g, int gx, int sx) {
	    int gy = parent.parent.data.getData(nodeId, gx);
	    int sy = -1;

	    if (gy >= 0) { // Ignore missing values
		double rsy = height - yscale * (gy - gy0);

		// Ignore problem values
		if (rsy >= -1e6 && rsy <= 1e6)
		    sy = (int)(rsy + 0.5);

		if (lastsy >= 0 && sy >= 0)
		    g.drawLine(lastsx, lastsy, sx, sy);
	    }
	    lastsx = sx;
	    lastsy = sy;
	}
    }

    /* Update X-axis range in GUI */
    void updateXLabel() {
	parent.xLabel.setText("X: " + gx0 + " - " + gx1);
    }

    /* Ensure that graph is nicely positioned on screen. max is the largest 
       sample number received from any mote. */
    private void recenter(int max) {
	// New data will show up at the 3/4 point
	// The 2nd term ensures that gx1 will be >= max
	int scrollby = ((gx1 - gx0) >> 2) + (max - gx1);
	gx0 += scrollby;
	gx1 += scrollby;
	if (gx0 < 0) { // don't bother showing negative sample numbers
	    gx1 -= gx0;
	    gx0 = 0;
	}
	updateXLabel();
    }

    /* New data received. Redraw graph, scrolling if necessary */
    void newData() {
	int max = parent.parent.data.maxX();

	if (max > gx1 || max < gx0) // time to scroll
	    recenter(max);
	repaint();
    }

    /* User set the X-axis scale to newScale */
    void setScale(int newScale) {
	gx1 = gx0 + (MIN_WIDTH << newScale);
	scale = newScale;
	recenter(parent.parent.data.maxX());
	repaint();
    }

    /* User attempted to set Y-axis range to newy0..newy1. Refuse bogus
       values (return false), or accept, redraw and return true. */
    boolean setYAxis(int newy0, int newy1) {
	if (newy0 >= newy1 || newy0 < 0 || newy0 > 65535 ||
	    newy1 < 0 || newy1 > 65535)
	    return false;
	gy0 = newy0;
	gy1 = newy1;
	repaint();
	return true;
    }
}

--- NEW FILE: Makefile ---
GEN=OscilloscopeMsg.java Constants.java

all: oscilloscope.jar

oscilloscope.jar: Oscilloscope.class
	jar cf $@ *.class

OscilloscopeMsg.java: ../MultihopOscilloscope.h
	mig -target=null -java-classname=OscilloscopeMsg java ../MultihopOscilloscope.h oscilloscope -o $@

Constants.java: ../MultihopOscilloscope.h
	ncg -target=null -java-classname=Constants java ../MultihopOscilloscope.h NREADINGS DEFAULT_INTERVAL -o $@

Oscilloscope.class: $(wildcard *.java) $(GEN)
	javac *.java

clean:
	rm -f *.class $(GEN)

veryclean: clean
	rm oscilloscope.jar

--- NEW FILE: Node.java ---
/*
 * Copyright (c) 2006 Intel Corporation
 * All rights reserved.
 *
 * This file is distributed under the terms in the attached INTEL-LICENSE     
 * file. If you do not find these files, copies can be found by writing to
 * Intel Research Berkeley, 2150 Shattuck Avenue, Suite 1300, Berkeley, CA, 
 * 94704.  Attention:  Intel License Inquiry.
 */

/**
 * Class holding all data received from a mote.
 */
class Node {
    /* Data is hold in an array whose size is a multiple of INCREMENT, and
       INCREMENT itself must be a multiple of Constant.NREADINGS. This
       simplifies handling the extension and clipping of old data
       (see setEnd) */
    final static int INCREMENT = 100 * Constants.NREADINGS;
    final static int MAX_SIZE = 100 * INCREMENT; // Must be multiple of INCREMENT

    /* The mote's identifier */
    int id;

    /* Data received from the mote. data[0] is the dataStart'th sample
       Indexes 0 through dataEnd - dataStart - 1 hold data.
       Samples are 16-bit unsigned numbers, -1 indicates missing data. */
    int[] data;
    int dataStart, dataEnd;

    Node(int _id) {
	id = _id;
    }

    /* Update data to hold received samples newDataIndex .. newEnd.
       If we receive data with a lower index, we discard newer data
       (we assume the mote rebooted). */
    private void setEnd(int newDataIndex, int newEnd) {
	if (newDataIndex < dataStart || data == null) {
	    /* New data is before the start of what we have. Just throw it
	       all away and start again */
	    dataStart = newDataIndex;
	    data = new int[INCREMENT];
	}
	if (newEnd > dataStart + data.length) {
	    /* Try extending first */
	    if (data.length < MAX_SIZE) {
		int newLength = (newEnd - dataStart + INCREMENT - 1) / INCREMENT * INCREMENT;
		if (newLength >= MAX_SIZE)
		    newLength = MAX_SIZE;

		int[] newData = new int[newLength];
		System.arraycopy(data, 0, newData, 0, data.length);
		data = newData;

	    }
	    if (newEnd > dataStart + data.length) {
		/* Still doesn't fit. Squish.
		   We assume INCREMENT >= (newEnd - newDataIndex), and ensure
		   that dataStart + data.length - INCREMENT = newDataIndex */
		int newStart = newDataIndex + INCREMENT - data.length;

		if (dataStart + data.length > newStart)
		    System.arraycopy(data, newStart - dataStart, data, 0,
				     data.length - (newStart - dataStart));
		dataStart = newStart;
	    }
	}
	/* Mark any missing data as invalid */
	for (int i = dataEnd < dataStart ? dataStart : dataEnd;
	     i < newDataIndex; i++)
	    data[i - dataStart] = -1;

	/* If we receive a count less than the old count, we assume the old
	   data is invalid */
	dataEnd = newEnd;

    }

    /* Data received containing NREADINGS samples from messageId * NREADINGS 
       onwards */
    void update(int messageId, int readings[]) {
	int start = messageId * Constants.NREADINGS;
	setEnd(start, start + Constants.NREADINGS);
	for (int i = 0; i < readings.length; i++)
	    data[start - dataStart + i] = readings[i];
    }

    /* Return value of sample x, or -1 for missing data */
    int getData(int x) {
	if (x < dataStart || x >= dataEnd)
	    return -1;
	else
	    return data[x - dataStart];
    }

    /* Return number of last known sample */
    int maxX() {
	return dataEnd - 1;
    }
}

--- NEW FILE: Oscilloscope.java ---
/*
 * Copyright (c) 2006 Intel Corporation
 * All rights reserved.
 *
 * This file is distributed under the terms in the attached INTEL-LICENSE     
 * file. If you do not find these files, copies can be found by writing to
 * Intel Research Berkeley, 2150 Shattuck Avenue, Suite 1300, Berkeley, CA, 
 * 94704.  Attention:  Intel License Inquiry.
 */

import net.tinyos.message.*;
import net.tinyos.util.*;
import java.io.*;

/* The "Oscilloscope" demo app. Displays graphs showing data received from
   the Oscilloscope mote application, and allows the user to:
   - zoom in or out on the X axis
   - set the scale on the Y axis
   - change the sampling period
   - change the color of each mote's graph
   - clear all data

   This application is in three parts:
   - the Node and Data objects store data received from the motes and support
     simple queries
   - the Window and Graph and miscellaneous support objects implement the
     GUI and graph drawing
   - the Oscilloscope object talks to the motes and coordinates the other
     objects

   Synchronization is handled through the Oscilloscope object. Any operation
   that reads or writes the mote data must be synchronized on Oscilloscope.
   Note that the messageReceived method below is synchronized, so no further
   synchronization is needed when updating state based on received messages.
*/
public class Oscilloscope implements MessageListener
{
    MoteIF mote;
    Data data;
    Window window;

    /* The current sampling period. If we receive a message from a mote
       with a newer version, we update our interval. If we receive a message
       with an older version, we broadcast a message with the current interval
       and version. If the user changes the interval, we increment the
       version and broadcast the new interval and version. */
    int interval = Constants.DEFAULT_INTERVAL;
    int version = -1;

    /* Main entry point */
    void run() {
	data = new Data(this);
	window = new Window(this);
	window.setup();
	mote = new MoteIF(PrintStreamMessenger.err);
	mote.registerListener(new OscilloscopeMsg(), this);
    }

    /* The data object has informed us that nodeId is a previously unknown
       mote. Update the GUI. */
    void newNode(int nodeId) {
	window.newNode(nodeId);
    }

    synchronized public void messageReceived(int dest_addr, Message msg) {
	if (msg instanceof OscilloscopeMsg) {
	    OscilloscopeMsg omsg = (OscilloscopeMsg)msg;

	    /* Update interval and mote data */
	    periodUpdate(omsg.get_version(), omsg.get_interval());
	    data.update(omsg.get_id(), omsg.get_count(), omsg.get_readings());

	    /* Inform the GUI that new data showed up */
	    window.newData();
	}
    }

    /* A potentially new version and interval has been received from the
       mote */
    void periodUpdate(int moteVersion, int moteInterval) {
	if (moteVersion > version) {
	    /* It's new. Update our vision of the interval. */
	    version = moteVersion;
	    interval = moteInterval;
	    window.updateSamplePeriod();
	}
	else if (moteVersion < version) {
	    /* It's old. Update the mote's vision of the interval. */
	    sendInterval();
	}
    }

    /* The user wants to set the interval to newPeriod. Refuse bogus values
       and return false, or accept the change, broadcast it, and return
       true */
    synchronized boolean setInterval(int newPeriod) {
	if (newPeriod < 1 || newPeriod > 65535)
	    return false;
	interval = newPeriod;
	version++;
	sendInterval();
	return true;
    }

    /* Broadcast a version+interval message. */
    void sendInterval() {
	OscilloscopeMsg omsg = new OscilloscopeMsg();

	omsg.set_version(version);
	omsg.set_interval(interval);
	try {
	    mote.send(MoteIF.TOS_BCAST_ADDR, omsg);
	}
	catch (IOException e) {
	    window.error("Cannot send message to mote");
	}
    }

    /* User wants to clear all data. */
    void clear() {
	data = new Data(this);
    }

    public static void main(String[] args) {
	Oscilloscope me = new Oscilloscope();
	me.run();
    }
}

--- NEW FILE: Window.java ---
/*
 * Copyright (c) 2006 Intel Corporation
 * All rights reserved.
 *
 * This file is distributed under the terms in the attached INTEL-LICENSE     
 * file. If you do not find these files, copies can be found by writing to
 * Intel Research Berkeley, 2150 Shattuck Avenue, Suite 1300, Berkeley, CA, 
 * 94704.  Attention:  Intel License Inquiry.
 */

import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

/* The main GUI object. Build the GUI and coordinate all user activities */
class Window
{
    Oscilloscope parent;
    Graph graph;

    Font smallFont = new Font("Dialog", Font.PLAIN, 8);
    Font boldFont = new Font("Dialog", Font.BOLD, 12);
    Font normalFont = new Font("Dialog", Font.PLAIN, 12);
    MoteTableModel moteListModel; // GUI view of mote list
    JLabel xLabel; // Label displaying X axis range
    JTextField sampleText, yText; // inputs for sample period and Y axis range
    JFrame frame;

    Window(Oscilloscope parent) {
	this.parent = parent;
    }

    /* A model for the mote table, and general utility operations on the mote
       list */
    class MoteTableModel extends AbstractTableModel {
	private ArrayList motes = new ArrayList();
	private ArrayList colors = new ArrayList();

	/* Initial mote colors cycle through this list. Add more colors if
	   you want. */
	private Color[] cycle = {
	    Color.RED, Color.WHITE, Color.GREEN, Color.MAGENTA,
	    Color.YELLOW, Color.GRAY, Color.YELLOW
	};
	int cycleIndex;

	/* TableModel methods for achieving our table appearance */
	public String getColumnName(int col) {
	    if (col == 0)
		return "Mote";
	    else
		return "Color";
	}
	public int getColumnCount() { return 2; }
	public synchronized int getRowCount() { return motes.size(); }
	public synchronized Object getValueAt(int row, int col) {
	    if (col == 0)
		return motes.get(row);
	    else
		return colors.get(row);
	}
        public Class getColumnClass(int col) {
            return getValueAt(0, col).getClass();
        }
	public boolean isCellEditable(int row, int col) { return col == 1; }
        public synchronized void setValueAt(Object value, int row, int col) {
	    colors.set(row, value);
            fireTableCellUpdated(row, col);
	    graph.repaint();
        }

	/* Return mote id of i'th mote */
	int get(int i) { return ((Integer)motes.get(i)).intValue(); }
	
	/* Return color of i'th mote */
	Color getColor(int i)  { return (Color)colors.get(i); }

	/* Return number of motes */
	int size() { return motes.size(); }

	/* Add a new mote */
	synchronized void newNode(int nodeId) {
	    /* Shock, horror. No binary search. */
	    int i, len = motes.size();

	    for (i = 0; ; i++)
		if (i == len || nodeId < get(i)) {
		    motes.add(i, new Integer(nodeId));
		    // Cycle through a set of initial colors
		    colors.add(i, cycle[cycleIndex++ % cycle.length]);
		    break;
		}
	    fireTableRowsInserted(i, i);
	}

	/* Remove all motes */
	void clear() {
	    motes = new ArrayList();
	    colors = new ArrayList();
	    fireTableDataChanged();
	}
    }

    /* A simple full-color cell */
    static class MoteColor extends JLabel implements TableCellRenderer {
	public MoteColor() { setOpaque(true); }
	public Component getTableCellRendererComponent
	    (JTable table, Object color,
	     boolean isSelected, boolean hasFocus, int row, int column) {
	    setBackground((Color)color);
	    return this;
	}
    }

    /* Convenience methods for making buttons, labels and textfields.
       Simplifies code and ensures a consistent style. */

    JButton makeButton(String label, ActionListener action) {
	JButton button = new JButton();
        button.setText(label);
        button.setFont(boldFont);
	button.addActionListener(action);
	return button;
    }

    JLabel makeLabel(String txt, int alignment) {
	JLabel label = new JLabel(txt, alignment);
	label.setFont(boldFont);
	return label;
    }

    JLabel makeSmallLabel(String txt, int alignment) {
	JLabel label = new JLabel(txt, alignment);
	label.setFont(smallFont);
	return label;
    }

    JTextField makeTextField(int columns, ActionListener action) {
	JTextField tf = new JTextField(columns);
	tf.setFont(normalFont);
	tf.setMaximumSize(tf.getPreferredSize());
	tf.addActionListener(action);
	return tf;
    }

    /* Build the GUI */
    void setup() {
	JPanel main = new JPanel(new BorderLayout());

	main.setMinimumSize(new Dimension(500, 250));
	main.setPreferredSize(new Dimension(800, 400));

	// Three panels: mote list, graph, controls
	moteListModel = new  MoteTableModel();
	JTable moteList = new JTable(moteListModel);
	moteList.setDefaultRenderer(Color.class, new MoteColor());
	moteList.setDefaultEditor(Color.class, new ColorCellEditor("Pick Mote Color"));
	moteList.setPreferredScrollableViewportSize(new Dimension(100, 400));
	JScrollPane motePanel = new JScrollPane();
	motePanel.getViewport().add(moteList, null);
	main.add(motePanel, BorderLayout.WEST);

	graph = new Graph(this);
	main.add(graph, BorderLayout.CENTER);

	// Controls. Organised using box layouts.

	// Sample period.
	JLabel sampleLabel = makeLabel("Sample period (ms):", JLabel.RIGHT);
	sampleText = makeTextField(6, new ActionListener() {
		public void actionPerformed(ActionEvent e) { setSamplePeriod(); }
	    } );
	updateSamplePeriod();

	// Clear data.
	JButton clearButton = makeButton("Clear data", new ActionListener() {
		public void actionPerformed(ActionEvent e) { clearData(); }
	    } );

	// Adjust X-axis zoom.
	Box xControl = new Box(BoxLayout.Y_AXIS);
	xLabel = makeLabel("", JLabel.CENTER);
	final JSlider xSlider = new JSlider(JSlider.HORIZONTAL, 0, 8, graph.scale);
	Hashtable xTable = new Hashtable();
	for (int i = 0; i <= 8; i += 2)
	    xTable.put(new Integer(i),
		       makeSmallLabel("" + (Graph.MIN_WIDTH << i),
				      JLabel.CENTER));
	xSlider.setLabelTable(xTable);
	xSlider.setPaintLabels(true);
	graph.updateXLabel();
	graph.setScale(graph.scale);
	xSlider.addChangeListener(new ChangeListener() {
		public void stateChanged(ChangeEvent e) {
		    //if (!xSlider.getValueIsAdjusting())
			graph.setScale((int)xSlider.getValue());
		}
	    });
	xControl.add(xLabel);
	xControl.add(xSlider);

	// Adjust Y-axis range.
	JLabel yLabel = makeLabel("Y:", JLabel.RIGHT);
	yText = makeTextField(12, new ActionListener() {
		public void actionPerformed(ActionEvent e) { setYAxis(); }
	    } );
	yText.setText(graph.gy0 + " - " + graph.gy1);

	Box controls = new Box(BoxLayout.X_AXIS);
	controls.add(clearButton);
	controls.add(Box.createHorizontalGlue());
	controls.add(Box.createRigidArea(new Dimension(20, 0)));
	controls.add(sampleLabel);
	controls.add(sampleText);
	controls.add(Box.createHorizontalGlue());
	controls.add(Box.createRigidArea(new Dimension(20, 0)));
	controls.add(xControl);
	controls.add(yLabel);
	controls.add(yText);
	main.add(controls, BorderLayout.SOUTH);

	// The frame part
	frame = new JFrame("Oscilloscope");
	frame.setSize(main.getPreferredSize());
	frame.getContentPane().add(main);
	frame.setVisible(true);
	frame.addWindowListener(new WindowAdapter() {
		public void windowClosing(WindowEvent e) { System.exit(0); }
	    });
    }

    /* User operation: clear data */
    void clearData() {
	synchronized (parent) {
	    moteListModel.clear();
	    parent.clear();
	    graph.newData();
	}
    }

    /* User operation: set Y-axis range. */
    void setYAxis() {
	String val = yText.getText();

	try {
	    int dash = val.indexOf('-');
	    if (dash >= 0) {
		String min = val.substring(0, dash).trim();
		String max = val.substring(dash + 1).trim();

		if (!graph.setYAxis(Integer.parseInt(min), Integer.parseInt(max)))
		    error("Invalid range " + min + " - " + max + " (expected values between 0 and 65535)");
		return;
	    }
	}
	catch (NumberFormatException e) { }
	error("Invalid range " + val + " (expected NN-MM)");
    }

    /* User operation: set sample period. */
    void setSamplePeriod() {
	String periodS = sampleText.getText().trim();
	try {
	    int newPeriod = Integer.parseInt(periodS);
	    if (parent.setInterval(newPeriod))
		return;
	}
	catch (NumberFormatException e) { }
	error("Invalid sample period " + periodS);
    }

    /* Notification: sample period changed. */
    void updateSamplePeriod() {
	sampleText.setText("" + parent.interval);
    }

    /* Notification: new node. */
    void newNode(int nodeId) {
	moteListModel.newNode(nodeId);
    }

    /* Notification: new data. */
    void newData() {
	graph.newData();
    }

    void error(String msg) {
	JOptionPane.showMessageDialog(frame, msg, "Error",
				      JOptionPane.ERROR_MESSAGE);
    }
}

--- NEW FILE: oscilloscope.jar ---
PK


Created-By: 0.92-gcc-4.0

PK

nŒ0±¯¯àÔé^ÿPä–¡ùÏ’{ÒX¡ë1`†…Ë6=̾-©Rß\5qrñF:&„õ†Ù¤}E–·Ñ˜BžçÂзŸ2<»aTe\ƒÀZ¨€úž@%ŽW

nP°	7óüPK

©èÆcyy¢à©‚g
2
‚E»l;}¹=ãÈHÇ"™•žCGáPÛbðb“î±ií&_­xn	X0-S,@¯e—
Šo	‡`™è6C k—8Coδøæá~;ïŒB™<á].²¼\^+™Âv¶ò!9#z´æMaU!¬—VOÈÚû¶Å-Á°¤_lÎCfZyòÑÆÏ	ˆ_5ŠEîºS©TšaNÏyã×kešmÝT"Å0ÙJqEʬLÑ/KiÝùŸ¼´Ì‹´É£µ«[ö¡Säë¦\u¤	1#y5<Ç¢†›ÔÁ€†!ÐcìoS’aø‚·ÆöÁ¶]Nª	5‘OÍ2€AÛ°,îdˆërW’/ixe
+ȶIJKȪ†5¬3ô\\©§	Î0ÚØÑrÁŽQ€ñF€ŠH6)ˆ$øÞt¸¼fKؼÄà×åþ’—ˆ í¼kE‡‚¯šFÙÞeø¤·“Zë$mQvr¹(LÛÊ™®àÖõá&ùÔ#ùô¹\¬ØN‰;o
Ór怞—#¬þ×Ðmé%MÈ(•.öÂ0¡_×í6Æéu&éôÃG_Ò$½ï|R dòa˜Î[t;B'd×cbUw*è8…¯
…~u¾Ž}/HTѵKT ²¯¸±)þ3hÒVÑM‘é
zÎÐ{)Ø:G©¨b€jöR½Aª˜ ¾æ1AÊŒê6îœ÷ -)AâóþKÑá9§éÅØ94JctíŽ%~ ï~ÙB=E¥00G3Í÷uâ.Õ«¥ÁïE”X<ÚZ#Dà	LþK;:…{m ¾vÐûxÐ=;i†Rwº÷Œ¢H“
Ó¯.Ô>Ý5óPK

¡½p–öÖ
>V^àlBÛåYŸ{eª¯±Š³7‚ñüÊóôSZn¬¤—"yD”°cùi~ÜÉe¬áñ?å4Þ‹oÑÀ|ËÝåHíO¡¾&ëh/wžP‚¾§„WttA&s
õ;[\‘~µÉΰ`þÇè:sÁñ¬ÕÂ4Y[ÅZûPK




&7¥SÙœ™Ê­5“ý“† w9jk¢ËWtŠ
|ެsu»H|ÜêH–­îì\ÝÆ]Ñ¦•:Ú—6EW-Wàm[º~ƒˆ:DßOöúºhsç
Y®*ð²º@«gpžMç²ù!òZ¶ÛLZ
Ü}fÆJåxbë©xz ‘²-V¢geÚ@"žc¸îAG[iædÈ™øûûâfÎb¤[™l"¢í`íZjS:ÎõòX"e­êïÝde:ÍM²ÃÓknµš’‰>Á`ì"ób³ÁÈ5ØIMtgkKÈèÙîŒe¥ÖÓp0Z-
ºx–QãŠû¬D*‘;›WCNRž‘@š3æ@G.ÃTPWÂï‚æÆ‚0i¦z
šÑ¨˜Òg&R¹¦to_:e§izÉsSÓ§—®¥ƒ	ÖÃq¥Õœ*šQÊ–íÖ(¤v}ÌÜd%™·ŒÕMïV¦*è)k ÙÌ™\ËZ¹ŽÂ…ëœ@òüç±8:Òý™n«5!ë>ÛÉ©âÓÀwð]"jà{Âmn½uB6‚*ç	×%dLOø>~` qç‹ø|ˆÁcØœNå:­Á\ÌJ÷çtüÐÀÓø‘gð¬l{ÎÀóø±ŽŸˆÏŸÊH^À‹
¦•H—˜É@7â:^6ð
^5ð3üÜÀ/pÈ@9=Øbà—x]Ç~%aüoø
.2Џ‹
üV6ý‡t¼eà÷øƒ?âO¶â"fâ•/ëO$ã’cø3þ"ämÅß¤Á’jŸmî¸F A½c )þÞÅß
$„ûþI$¬_4›tvýlÿ2ðoüÇÀ€„ò_ïá°Î;V9ñ †¥qÞ™Sr°Á†ì
ªX
,†’Ðr°BóÙ^3™lMK‰—©Š€j•t%ì+Åð9^²‚9ÁqVíË?J‰›[‚Gƒk‚‡ÿkÅF±,ÝŸŠ3¸Yã=÷XéÞ†5V7Û-/†8cÌÇ\´³Gª
Ì,ÔÖVü±ÕÙîD2™Îv§û,šRÔmJ'Ó¬mYliÓJ¶ÂØ‘+0:ZÓ‚—źg3MËɈæÂQ8¹7³b‰l®-”×>¥s»{:‚N{›{äJ.0Òlb›U(—âyÆ—Ãè]T`DëVD;[¨.ù‘>µ}}m¦”›+™î‘NÔ,‰tõIýhÁf{æ6ûúxs
êKÜzíQ"rô^S<SÉeO.=ÚÁ+ƒG+Rãø`̾ÀÆcõToŠ•ÔžNH‰—jÐNÇw:M·2vmiãDt:º:/»ÓÆD TÜÒÑÇ—’”ŸÝ¼Ý1iâ´ªš¹Úkòi¦g,û¹ƒÙ|iჼÇa¢|_8—³2¬ä<6nÞÆùªqóÕœ·O˜`Âþ5ãæœwNÐÿà¸ùZR>ìW¯õÎØåŒç9ãùÎȇ‚=^èÌ78#)ö¸ÉËùck§‹ÜmÐÈq-ÊCñûâüA™Ì\þ`ÜʼnÚ‹2u'<!UVöB¨!™
“hü¹ãêÆáôt‘–‰Ôßבú°ßº6“ž/i-¦ „j„1u¢óp*NãûãJr&æS‡眵<gÇ©aÐï·â$I÷ͺí@òLJ³-ÕÞVAm½Ú>äðÖJÝ;ïLþ¶÷ÕQ*û*GCñUÛ±oí9jçbþ’ŝaÇã´jÙ¦§ÅçpwI—KùëEÊÙ¸.{c(´SBÚÊCáøC®xCSG˜M²îM–Ž@ËèLZ+VÅ	Ìm3ÛŠ*,gÖVPÎg¨ããêéÏ8€Š®:ÚñïÅ´p•ûX¤UiyL_ä®Ò³®©rç1ãúÍտ޻~rjSUc+輩
‘*›™S-½ë?eñ†Î?ˆ ƒ×ø}üŸµQçQ;ËLFÔ€ê÷ï†î÷GÔq 	¨ÉˆÐnƒ[•qø!„*&Mï`
´aNnJëÓv1Eô€®í†×Ñ÷`r@÷ˆEŸH‹Š;¶D—Þ€Fï-жD<ñçÙ…h(àÉãT¿ocÄ
@C×>Ì«˜ŸÇ‘¿?…5ž¸xó8-L6à%¡¶jÚŒxöÞ;Vb†X1;…pNsXݵ¬¿y¬À…¬fæ-ÊÌÅ0Äo£ôÞÉ¥¸åÇë¸Wâ>\…ýØŽ—q5ÞÂ0ÞÆµx‡ß¦ª¼X:Õu+oM“Îv
¨yœ!äÌí*‹äÍ=¬j;JÚ.Q"+‹ŠJ{ÖLâçk5nÀ)¸‘Ý@ðßÄhnfÝÜÄ:¹…:ÅÞe.çõ…*mMòYL²øfš+x­ò<Îsé³ÛÁ~ö¢äø¶ê˜¢YùÆl)F¬m“î¤Å7.uq^³Qú9M8[ål­.2»•äqΘ»m¼>FÄ,<Ο`?~üZXÂφf<ÅõmÅèÚy(9`Ð÷^,ÉcéYÕ…lN²›­<'ª÷a=5yšnßÝÓ¬’gp<že•<GoÏSvI1Èv§åaâö/¨´C©–¶x„ÃcX“ñ"»ÔK´þ2­¿BÙ¥øˆc}mK
ÃÕ5;1£úvv늩»0¹ævxk*¦î†æºW|yC5Òwi_kÞ…f{ˆ
ö5–Âë|Ux'ñ#¨–ß@e,dyl_Ææ+M²&®•ã.ÎkÀÿ

Iy“‚¢¶2ÉçÐðÍx‰v¼¢-^“¯ëí¾CA#éF¬¥µ‚F2º· e¤XFÖ¥
Ì×ÂñõšnÒ¥àÎúü°’P:°ëÉtZž> Í~œ‘)OÞ@§Nb÷


øYhÁ(‰ŠÅ¢5PˆÅŠÔh—îJJKÊÖH⑃W/ÞiH¸p1¡A
áâÁÄ“ÿ
Ñ	.«wS}õa{E"ýêÛp®Sþ6ø'4§ÞÌvÕ*x‡p
Ï^f¯¥àwU0,í&Ee¸ yz¢šßµ·ßÕvÕk\;Ûøaö;¸õ6~p$(¤fFëmÝBÒd‰#‡º)°´ªV„¥"{wnJM‰jÁ
õtÇäš“mþÞ×;ªP꣙¥‚~ŠF`+z¡¡ŸhW0€ûô+Má’¸Œ²,`\ÅKÜÀ+ÜÄŒ`·ñcø‚»øŠ{øFüƱáÑQ[Oš†/Æ	z¢º¦y2;ð8‡x€÷íU¬÷à™¾
!âa<"»†ÓµBQÚµ¸ƒÓµ}6®¬ÃåØE¤Õo¹4ÝÚº²§òNºjX„Ĉˁr³žä–æ,Ñny¬gþ

ÔŠŽ|+
b£tܳIE_è°4DÊŽ¯4$r¶ò3¾e¯;^¦¬<Ï,ªÌ4¯²ShT0}SC47Á•çèšeœ5
­¹[ÁŽºVËö•»j–4hY
úªr=˱i<fÙ–IC85x“XãNã9ËV3ÕòŠrçÍ•5a·JkÝVk3E$•ûxƒÍ
•WÖª*hH¦²¯d[_/È#£¢\Ë),THœP-©l€Õî)?»MSܦ­§ìBSÛ’/)ӕĘùìK-åf¦dÚÅÌœïZv1p›sªn^MY½{ÖË[¥’ã坊:)öÞÁiŒlQâIîD{ßÀ†™—zêŒà¤8­áàk‹ £d [
ÎÜ3™#Þ›zÕLà8?,' !…~Ù£?6{°²Íƒ•#¬§‚µöœÊ3.ÄНބ–îŽ`¡tháTjx—²üÌÑQ„@íÄÙíçµòübz‘Eb´l ʽ.ûÖ
´¥A[
1ªÚ†±‰Ž
tRÙ¹…øâ&ºÒ5t7]B”òžÐÍ1ÎÖÎaïSÏ9k¸í'ëÄ"ð‰zš±àâZ9dÇùì|ãÉ2™F¸žþñprøwÄgÒ#5ìáï­ö*Êí>þöów †ƒuò‡vóûˆßöqrœ"Ç+ÌöUæ3G=¿áìIHÒ„?Åþt‚)M–䘉gˆw?Ag
7]ô’9pr˜Gp7q·XƒE–z	I\d„uW3t$÷&"?2=Gž¢%ü\ü…Ò‹t¹+žé"ò|W¦ü·¢<}ˆ¯ˆ{úËÛÕ-[Âm+7$”k8*k¨†cRÈž#C5$Ÿ¡cZBëé­áí|€‘ô³;£z at YFlÞV¨óiáb

©
WIjÃÃ{9¶^qcQS¼Û3Ó…7¼h­IÛ
±?èb´Ù¼o‡J:)g9ù~‘Þ§Ëè—`pFß?ƒ»VD£Q']é'ô£†£.‘öÓCŒBF(íÇÔ¤¼2QÈOÌ£’¹èÊœ¼”/nwD,í
üTÔéÑ^$˜,}µ WäCtvpAÊêI5sJ
ò¾çz]C/›4fÔYû Å^’'O/É	»Å!--É*öëÎPõaZ}¾êß	Çl‹±ÏðãØÏ<*¯”çM¡Ö§3¶Æá˜šŽê½§_ø±YµäÀÒ¾£Ø›·)f“UñSÔ…Kò^\h
´•vR/1êÓf»0¿¯b¾›öá­û%xzác[Âß±°—.®RÃGð0ú2÷¿&½ä 5S›ÆÝ«sw‚Û€±9ì¯MÝL´…D”Á©ûÊimÚjKØX#[
ni»¶[ã>P•6²FöÔ¤
#mD£>X•6‚´5¸»‘vÒÞ¯qûunÙñGÔ
î¶5rÔ ÷““ð<¨‘tò.Pùªd.´³{”ì²Vœ
³JžøU²Wâ|E\ÛF¸ö"nÓF¸Ž"nóF¸-ºß6Þ§Ý•K[yˆÀ*uzéÖ3¥8~¶]½õi%¼2Cä—h݃æð¢9&è1Œ	:Eg0>EïàøñâHfÌŽ±(î0y`§Àz¼i°fÀ{¬càÍ‚%w‚.S’ÖH¦›8ÿ>Çø-0F
k¤'ñ³õaÒ¿f§¶á°m×ÊСùÎ
ï‘ûyqÇtÿÖoëôxŽð{„´ÇÇïlv–lìµ³h{1çË1Gô˜ÛoÓð%¼€€/#à+XIÑBE‘üwÀÂ'.~mÚ®Ó\g‘€vWNhã¸

ސüj2êñèÖíq=%Byã‡0qŸ¡Rw†îÔmº3ÕäS.Uó,	éh;)fuÃIäñs¡äç]u•ëH/c!û—\
BßÄ®Ê6Ldmä´UA™(ÏÓj-0ØRò¨¸qÌc†bB pe¿yÝrO1”þåäˆXqʤ¹çõ21W“1Cº®©È뺣qÀiBú¨Ò63Ôq¬XÔ¤hÉ)º9X„æÉÚ#_#Vãà¬ñ†ÔŽ$)‹ä#ÉE,ýÄWIëølãìé¿Ñi’.E,“6èW±¥;лä|PK

(£R€\މ6Q!ÉÓ´Ú
çJ‰¨ð81C)pÕk^uÂÓå5ud¬eÒ»§õ2±Ð£G†tÝHu¼@ðè”kŽ*Í1C½²`¥’‘CãMÑvàš§h‹Îq;/`7¤ž¨#KYdŸÉÎ`ö‡_%oø¹á¯Hÿe§QLº”0GÞ¢ÿ[Àªé@÷’õ
PK

±Ê„Ås2w”.²©x#-A³(Ý·0*°§¦Iª	ûN˜y;Øô°߃‡k\4=¬Øeƒm;xæ ï!„Wpu_0€Z*%²Aµš¡m+„	µLÅԐðÿõIm„²­”U-Ì☡ì[	ÕYƏçN´ošæÚ££ŽfÂŒy²9pHÀýX'ºÑ”Û	TüaoŒÝuºn Ôn[¡
Z´Û¦h‹Öeò­`û,ع@)ø„ò¬èëdk䁯dW±öÔ'¥Y
ÎÁÎQ!WºDõµ¿87?ñâïT`=/¼ä+ôünáI~†D#ÿ~PK

1M­2`¶­ÕÐt´s0	Í“·I±FÌúö3Xý‰GÄâÈRÙO²3˜ýáWèÔülý	ìÉ¿ì$
qst¦èû°ª;Ð}
ñúPK

ËñØd5¬`ÛŠžêâó
PK

~£aš@LÅý
ÐP…ßÊÇï„ü^Áƒ¦ã!9î
Ö0Tðˆ‚?i˜-ÛfâQ©x\COÈ÷“BžRð´†z‘¨Ç3žÅs*þ,˜Ï)ø‹†yæó*^Pñ¢ÆÓ¿*xIÃ"<$—Å—eöÿTð/
K•W¼ªa9îSñ7zMÁë>†{CC+ÞTðo
m⸷4ûüGC‡hс·…¼#ä¿ô®¾'»Þòèô¡ìÓ°Àl">‹<*•(äÕp’­5•òT¦’"£*®¼SÄ|B4•Æ)¤«4^COˆø1›Ý¥‰BÊ5ª Ìü
MҐâ4™M•Ma°S–¦i ý„T	©²?FˆÈt•‚*Í`ch¦Bª4KvÏVé ^H¥j©N¡9
Õ|ɬٜŒ'Ó½-‘0ÓÍq#“13„	²´Ö莛Ɉ'”¥Œ´™ÈòJûêLO,Ofz’)³‰PM©Í¥}¥L˜ãËôñxkRÄ'¶o1¶
F¶A¼ªv'ãgQK$Ó,ê|Œïå#Ûc™¬{âÔöõ±D$Ù?«PF(h7ºEd²
>Аé%¢
GÙ\^×2Fo*n®5vZ¡Œ0[cf<"šowDJ7¥^s\«på¸Å±D,û9BE¨Ðöšuo3+%fÆfg_o·™¶Ue%z­æò¾l6™ ¬
9nˆÜ•M3~Ó°cÌmìÙ†e=ÙX2!09M5…Ê8Hâ\Áuí¯)ÛVSÜ)d_—ÆÝ<^ywCmŸM¥WfÌl_ŠP§øz⦑n1²‡›WÂËbœTyÚe‡f™Ž%ùL_*bdÍB¦’0û;m¯zCm'¬ÔL§%[+‹.’ZW²/Ýc¶Æ$ãœü9D$u\ŽkÙæ‚däжČx2Ê«tˆŽoà›:.r±óp>aR‘(èÔ€›t:”æêtÍc3Æz\ ç¦÷˜¨s£NèpŽ …£Ö	‰Pe^ÛåÉtÄL·Û“}YA^äî°[b½f"Á’•Fšh¡N‹µ²h‰Øß·GŸkËÈ2'û„<¾Û!ÊG€Ù,–Ò‘Uû£ÙŒÇWDbY‘œ¸&Ö³5(rAG­e-£å:5‹^SNíêI'ãq±Y§Z¡P«N+i‡½™‘ò´ûŠŽp!G¬™ÓäC&9ILÙYõfjuüWqʹšÎ%@Ç5ø‰ŽKp©Ž$´fÉÍ`ÄINWò0?•¬ùHÅ–'t|ߝ]ñC§6:J|u´»ÞЗÅV™ÍY§T§hë­§ŽNÛå}±¸ÖNB:uZMkt\«t:†ŽÕ©‹Öêt­Ói=®ÕinÖ{ž(Öéx:A|v"Án^ž¯ã26œN¢£¸4ƒõANf¥i#}^'ƒºÙ·\0ÙÍ£ó Õé‡úÈn§ã{ø¾N=’\2Ú¤S”Ö
¶@§ÍÓi®Uh¼Ž]¢íVZ£P\§^âªIRJ§/°u<c‹3Ĺ|õq®µ%¶ìÕ`šc	Cæ@ÊìÉš‘ ¯ô™™`·™í7ÍDðР‘ˆ_°`Þ‚¿ÀM„éÃNuZp«\+Ù=fJ‡b^gg}GGNÛ¨ŸësèäLA6é4 Áý¹øºt…ô…¶ë´ƒNátìå‚>vu÷çðkÈNãÞçÜ62sº?4êrÁ
#)컄™¡Oê̼qJÑˀʣfvMÚÜĽӌtÅv˜"[Ó>¶È-ÁÊu±Þ¾^Grj¨˜ ÀVXq:S‡‘0¢®6¥!ÇnïÏ<`yfŒ6ãBîUoïœ*¸fìbjqÿ‹ŒŸAZÌMF_<{¬™àú/-é
d‰’TÛ±04VÛ"úeñ}Ξ¶¯[÷Ñp¡ìVܳ‹ÄHooÊÄ5F,álÈÈëâxáëÎdƒû,’÷J—ã¦`>/Û‹\
%œ$¶—¬Vt¨£–N-^ä}æÀêMβM~“ôugÜÅ)Ò›Šå£—'½òº,¾¬òo ŒÙ&­»è´Ím{Çsu³©ré§ùvqt`¦Ës
_@«íkÊqiEfs²¿ÃÌd¸•:¯SÂâOYãE/
~À³M(ƒ‡ÇÆÚAPmŽŸ"þ’’ruÞÝ(u¿½åºý]–_w¾•ÚºÝPwñn™îÇX`ͽ˜†É`sæca&óù­æžÙÂ'zy¬ÎÁ„ÖY_ga\}íÍ(³ ×ϱ0¾þÆ<ê”2ݧ°î§¢§1îéÌãw£‹8ŸñqrÂuÕƒ˜Øé•@š€¦g²OÏ‚;y~õ'!”C8ò·?Îå9?T]„U¼&Á*ÂUƒðÛ

èÍX9ˆUJm¬ÅQjÉá¾J_¹z%æÔŽWúÑκtðr§¿ÅÂêòÃ*}ÖX8†µÐµ~§Ïs;J@µ°6 x-'àÖÉàžca}€ÏÙ®
(ƒ[8>PÆK+,œ°>PPd¬õŸh›çcóôNKN¶ÍÛÈ¢]5Àϳ~ƒr>»•,ôˆÑ%b´Ð¥6¼¶gb{2/Wïé°^gdœÅŸBºÌ}ù]eVÐü›œ¸EÃþ̓ˆíÆ^ÜÂ¥·ÕB\¦zù+±^æ|hRÆRbå,¤‡ýa.i3¹Å¹¹Ì寲„›J·•õœ¶Q–êç‚?ƒKþl.Ó¸Ð.ãR¹
×ñï¹ë±7â^Ü„G°Ï⼌[ñ!rT†Û¨·SË°—Vá.êÄÝtî¡ÜKiÜO§âÚ‰ßÓyx®ÀÃô<BÄŸè)<J/â1zÓ><áñáIOyfãiO=žñ,À³ž¥xγÏ{ÖâÏF¼èÙŠ¿z2xÉs&k
ý`ö€n¿[JÇBF¾,dÜé«»ç¨u÷̹s‹+\¹3xËîz3FuÄWxõUî¯qïzÕxáMæ_—?ð·|œô‚N¸¶µ×•sÁõ/©¾]u%\‡œÑÛ;몽œU;xÊÆJÔïÁ)/§^Šš|œÆYÇÛWÛé'é³Ú:;ýÎØu
¼¢u"ð¥aÖIE7ß_t­8D:Þfî»ò÷0	ïc|€|„ÅØþIŠ*A”¼8‹ÄŽë󖥨2qåB'}·±òíu¬òq&væ%ðªt–«Ò°6ãP…éym`o‚|GãPN:ªh<ªi"‚œ:GPKܐ?}	ŸM9}d—ÓoÆÎ|Qç¶ü­TÉpSø‹LºP³™/¥O’¡Ê—GoÙ*Uñ×®üé³x”ÓU7cF‚üÅ¿‡Ý!¾AäÝ®LÿWJöà«£wÈ–‹273ÇƒÝøšÜÞì“Ù\jçrŒ¦óøuæ-‚óo©;npÇ“Ü1åÿPK

³NH½Ù­ ÜhT«Å	ÌGõ7¡Ì`ܺC¯%.ìpßüƒ-Ûîè(dšˆ#ab©©VÇõLl"¯c‹!3iØ¿r¡J‰©Ø¬JI‚îûÂgÈΈ­ñ¦pö¦Á <Àúw2MõEPïóÁTÆ
ðÕ’óÖcÇs‡²Í+Ôfe–C–6Qˆú
:}O„<GÖ lWå€Qz+ÅÆˆ¼RFAVS•YÉ_þñuÅ?üDtŒXéåoP
Ðt¢”Ú•ÆŠª0R°ˆeÄ

i@‚\En†B4mµ)­‚L“!L'u2¥Q¼ ¾ùxÑå/>Ô.Ö’%‹g”ºÏdHÛ´v­œù¾3眽ÏþÎ7«ÿóç+
]tÕæ}_”»	£:g¦¹éOä×”ºg–ܵ¥ªFM÷®Åô¼gÕ3fµš-[n“kH*Þ L}̶Äz†±ç
§-+"åm׬˜,ò]³ÈE¡Ñð=žhH`HÃx¤áGIëk¶8(èÞÄS›*[hwhyÛ6ïèdÐn!UŸçô¤¼ãK¥§ê®c”ÜÕY±ì±õ§š¼–'~(7–ÍŽðX†Ó¹ìÈxš®g…ÑI*’Këë¶5e±vŸ?͑ۚØhâu™˜C‰üú‚bôÞµœ¦
2¨×gˆk–ý9å°„›âÑQqŒÙivQÈÉBúËùÇœ5,¹;Zo¨IÃ(—½û³
–ík¡òæÕóvÝt<Ø6W×ÈL3Í,c/¿Cûù¡CF«™BïÉÉ‚üñЁXLF˜»MüÁÛPp”«/O^†’|ŽäKô.#´²'cê_PõÀ"E=¸„΢Zâ}ÑÃKè*êê´¢Y¦¢Ëfݰ®‚m‚´ÑIF½ä³‡ã—½É"ɧ°ˆ<E¸góbOl±•ÛÍ/p’Nñc~†Þã8áçíäS‘^C¿·âÞç,u9ÙŠÛëÇE’Ïj`K{ô{ŒæMó£O“‹`öÇŸB“Œz¹{‹6fçÉìU½Dï¼ëרÏw^GÉ













































More information about the Tinyos-2-commits mailing list