From: klueska Date: Tue, 17 Jun 2008 21:10:35 +0000 (+0000) Subject: added java stuff to the TestCollection apps X-Git-Tag: release_tinyos_2_1_0_0~257 X-Git-Url: https://oss.titaniummirror.com/gitweb/?p=tinyos-2.x.git;a=commitdiff_plain;h=e317eb26a483de73fd5af581b0916d6b2835a250 added java stuff to the TestCollection apps --- diff --git a/apps/tosthreads/apps/TestCollection/java/ColorCellEditor.java b/apps/tosthreads/apps/TestCollection/java/ColorCellEditor.java new file mode 100644 index 00000000..f88b0b6f --- /dev/null +++ b/apps/tosthreads/apps/TestCollection/java/ColorCellEditor.java @@ -0,0 +1,55 @@ +/* + * 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; + } +} + diff --git a/apps/tosthreads/apps/TestCollection/java/Data.java b/apps/tosthreads/apps/TestCollection/java/Data.java new file mode 100644 index 00000000..ac35aa72 --- /dev/null +++ b/apps/tosthreads/apps/TestCollection/java/Data.java @@ -0,0 +1,74 @@ +/* + * 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; + } +} diff --git a/apps/tosthreads/apps/TestCollection/java/Graph.java b/apps/tosthreads/apps/TestCollection/java/Graph.java new file mode 100644 index 00000000..9a42c1c8 --- /dev/null +++ b/apps/tosthreads/apps/TestCollection/java/Graph.java @@ -0,0 +1,232 @@ +/* + * 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; + } +} diff --git a/apps/tosthreads/apps/TestCollection/java/Makefile b/apps/tosthreads/apps/TestCollection/java/Makefile new file mode 100644 index 00000000..55c2605b --- /dev/null +++ b/apps/tosthreads/apps/TestCollection/java/Makefile @@ -0,0 +1,21 @@ +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 diff --git a/apps/tosthreads/apps/TestCollection/java/Node.java b/apps/tosthreads/apps/TestCollection/java/Node.java new file mode 100644 index 00000000..cfe8db9e --- /dev/null +++ b/apps/tosthreads/apps/TestCollection/java/Node.java @@ -0,0 +1,101 @@ +/* + * 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; + } +} diff --git a/apps/tosthreads/apps/TestCollection/java/Oscilloscope.java b/apps/tosthreads/apps/TestCollection/java/Oscilloscope.java new file mode 100644 index 00000000..3db3741f --- /dev/null +++ b/apps/tosthreads/apps/TestCollection/java/Oscilloscope.java @@ -0,0 +1,128 @@ +/* + * 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(); + } +} diff --git a/apps/tosthreads/apps/TestCollection/java/Window.java b/apps/tosthreads/apps/TestCollection/java/Window.java new file mode 100644 index 00000000..d7979bf9 --- /dev/null +++ b/apps/tosthreads/apps/TestCollection/java/Window.java @@ -0,0 +1,294 @@ +/* + * 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); + } +} diff --git a/apps/tosthreads/apps/TestCollection/java/build.xml b/apps/tosthreads/apps/TestCollection/java/build.xml new file mode 100644 index 00000000..e8fa088b --- /dev/null +++ b/apps/tosthreads/apps/TestCollection/java/build.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/apps/tosthreads/apps/TestCollection/java/oscilloscope.jar b/apps/tosthreads/apps/TestCollection/java/oscilloscope.jar new file mode 100644 index 00000000..acf8fe3a Binary files /dev/null and b/apps/tosthreads/apps/TestCollection/java/oscilloscope.jar differ diff --git a/apps/tosthreads/apps/TestCollection/java/run b/apps/tosthreads/apps/TestCollection/java/run new file mode 100755 index 00000000..5b5df76f --- /dev/null +++ b/apps/tosthreads/apps/TestCollection/java/run @@ -0,0 +1,7 @@ +#!/bin/sh +if cygpath -w / >/dev/null 2>/dev/null; then + CLASSPATH="oscilloscope.jar;$CLASSPATH" +else + CLASSPATH="oscilloscope.jar:$CLASSPATH" +fi +java Oscilloscope diff --git a/apps/tosthreads/capps/TestCollection/java/ColorCellEditor.java b/apps/tosthreads/capps/TestCollection/java/ColorCellEditor.java new file mode 100644 index 00000000..f88b0b6f --- /dev/null +++ b/apps/tosthreads/capps/TestCollection/java/ColorCellEditor.java @@ -0,0 +1,55 @@ +/* + * 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; + } +} + diff --git a/apps/tosthreads/capps/TestCollection/java/Data.java b/apps/tosthreads/capps/TestCollection/java/Data.java new file mode 100644 index 00000000..ac35aa72 --- /dev/null +++ b/apps/tosthreads/capps/TestCollection/java/Data.java @@ -0,0 +1,74 @@ +/* + * 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; + } +} diff --git a/apps/tosthreads/capps/TestCollection/java/Graph.java b/apps/tosthreads/capps/TestCollection/java/Graph.java new file mode 100644 index 00000000..9a42c1c8 --- /dev/null +++ b/apps/tosthreads/capps/TestCollection/java/Graph.java @@ -0,0 +1,232 @@ +/* + * 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; + } +} diff --git a/apps/tosthreads/capps/TestCollection/java/Makefile b/apps/tosthreads/capps/TestCollection/java/Makefile new file mode 100644 index 00000000..55c2605b --- /dev/null +++ b/apps/tosthreads/capps/TestCollection/java/Makefile @@ -0,0 +1,21 @@ +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 diff --git a/apps/tosthreads/capps/TestCollection/java/Node.java b/apps/tosthreads/capps/TestCollection/java/Node.java new file mode 100644 index 00000000..cfe8db9e --- /dev/null +++ b/apps/tosthreads/capps/TestCollection/java/Node.java @@ -0,0 +1,101 @@ +/* + * 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; + } +} diff --git a/apps/tosthreads/capps/TestCollection/java/Oscilloscope.java b/apps/tosthreads/capps/TestCollection/java/Oscilloscope.java new file mode 100644 index 00000000..3db3741f --- /dev/null +++ b/apps/tosthreads/capps/TestCollection/java/Oscilloscope.java @@ -0,0 +1,128 @@ +/* + * 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(); + } +} diff --git a/apps/tosthreads/capps/TestCollection/java/Window.java b/apps/tosthreads/capps/TestCollection/java/Window.java new file mode 100644 index 00000000..d7979bf9 --- /dev/null +++ b/apps/tosthreads/capps/TestCollection/java/Window.java @@ -0,0 +1,294 @@ +/* + * 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); + } +} diff --git a/apps/tosthreads/capps/TestCollection/java/build.xml b/apps/tosthreads/capps/TestCollection/java/build.xml new file mode 100644 index 00000000..e8fa088b --- /dev/null +++ b/apps/tosthreads/capps/TestCollection/java/build.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/apps/tosthreads/capps/TestCollection/java/oscilloscope.jar b/apps/tosthreads/capps/TestCollection/java/oscilloscope.jar new file mode 100644 index 00000000..acf8fe3a Binary files /dev/null and b/apps/tosthreads/capps/TestCollection/java/oscilloscope.jar differ diff --git a/apps/tosthreads/capps/TestCollection/java/run b/apps/tosthreads/capps/TestCollection/java/run new file mode 100755 index 00000000..5b5df76f --- /dev/null +++ b/apps/tosthreads/capps/TestCollection/java/run @@ -0,0 +1,7 @@ +#!/bin/sh +if cygpath -w / >/dev/null 2>/dev/null; then + CLASSPATH="oscilloscope.jar;$CLASSPATH" +else + CLASSPATH="oscilloscope.jar:$CLASSPATH" +fi +java Oscilloscope