--- /dev/null
+COMPONENT=MultihopOscilloscopeAppC
+CFLAGS += -I$(TOSDIR)/lib/net/ -I$(TOSDIR)/lib/net/lqi -I.
+
+include $(MAKERULES)
--- /dev/null
+/*
+ * 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.
+ */
+
+/**
+ * @author David Gay
+ * @author Kyle Jamieson
+ */
+
+#ifndef MULTIHOP_OSCILLOSCOPE_H
+#define MULTIHOP_OSCILLOSCOPE_H
+
+enum {
+ /* Number of readings per message. If you increase this, you may have to
+ increase the message_t size. */
+ NREADINGS = 5,
+ /* Default sampling period. */
+ DEFAULT_INTERVAL = 1024,
+ AM_OSCILLOSCOPE = 0x93
+};
+
+typedef nx_struct oscilloscope {
+ nx_uint16_t version; /* Version of the interval. */
+ nx_uint16_t interval; /* Samping period. */
+ nx_uint16_t id; /* Mote id of sending mote. */
+ nx_uint16_t count; /* The readings are samples count * NREADINGS onwards */
+ nx_uint16_t readings[NREADINGS];
+} oscilloscope_t;
+
+#endif
--- /dev/null
+/*
+ * 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.
+ */
+
+/**
+ * MultihopOscilloscope demo application using the collection layer.
+ * See README.txt file in this directory and TEP 119: Collection.
+ *
+ * @author David Gay
+ * @author Kyle Jamieson
+ */
+
+configuration MultihopOscilloscopeAppC { }
+implementation {
+ components MainC, MultihopOscilloscopeC, NoLedsC, new TimerMilliC(),
+ new DemoSensorC() as Sensor;
+
+ //MainC.SoftwareInit -> Sensor;
+
+ MultihopOscilloscopeC.Boot -> MainC;
+ MultihopOscilloscopeC.Timer -> TimerMilliC;
+ MultihopOscilloscopeC.Read -> Sensor;
+ MultihopOscilloscopeC.Leds -> NoLedsC;
+
+ //
+ // Communication components. These are documented in TEP 113:
+ // Serial Communication, and TEP 119: Collection.
+ //
+ components CollectionC as Collector, // Collection layer
+ ActiveMessageC, // AM layer
+ new CollectionSenderC(AM_OSCILLOSCOPE), // Sends multihop RF
+ SerialActiveMessageC, // Serial messaging
+ new SerialAMSenderC(AM_OSCILLOSCOPE); // Sends to the serial port
+
+ MultihopOscilloscopeC.RadioControl -> ActiveMessageC;
+ MultihopOscilloscopeC.SerialControl -> SerialActiveMessageC;
+ MultihopOscilloscopeC.RoutingControl -> Collector;
+
+ MultihopOscilloscopeC.Send -> CollectionSenderC;
+ MultihopOscilloscopeC.SerialSend -> SerialAMSenderC.AMSend;
+ MultihopOscilloscopeC.Receive -> Collector.Receive;
+ MultihopOscilloscopeC.RootControl -> Collector;
+
+ components new PoolC(message_t, 10) as UARTMessagePoolP,
+ new QueueC(message_t*, 10) as UARTQueueP;
+
+ MultihopOscilloscopeC.UARTMessagePool -> UARTMessagePoolP;
+ MultihopOscilloscopeC.UARTQueue -> UARTQueueP;
+
+
+
+}
--- /dev/null
+/*
+ * 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.
+ */
+
+/**
+ * MultihopOscilloscope demo application using the collection layer.
+ * See README.txt file in this directory and TEP 119: Collection.
+ *
+ * @author David Gay
+ * @author Kyle Jamieson
+ */
+
+#include "Timer.h"
+#include "MultihopOscilloscope.h"
+
+module MultihopOscilloscopeC {
+ uses {
+ // Interfaces for initialization:
+ interface Boot;
+ interface SplitControl as RadioControl;
+ interface SplitControl as SerialControl;
+ interface StdControl as RoutingControl;
+
+ // Interfaces for communication, multihop and serial:
+ interface Send;
+ interface Receive as Snoop;
+ interface Receive;
+ interface AMSend as SerialSend;
+ interface CollectionPacket;
+ interface RootControl;
+
+ interface Queue<message_t *> as UARTQueue;
+ interface Pool<message_t> as UARTMessagePool;
+
+ // Miscalleny:
+ interface Timer<TMilli>;
+ interface Read<uint16_t>;
+ interface Leds;
+ }
+}
+
+implementation {
+ task void uartSendTask();
+ static void startTimer();
+ static void fatal_problem();
+ static void report_problem();
+ static void report_sent();
+ static void report_received();
+
+ uint8_t uartlen;
+ message_t sendbuf;
+ message_t uartbuf;
+ bool sendbusy=FALSE, uartbusy=FALSE;
+
+ /* Current local state - interval, version and accumulated readings */
+ oscilloscope_t local;
+
+ uint8_t reading; /* 0 to NREADINGS */
+
+ /* When we head an Oscilloscope message, we check it's sample count. If
+ it's ahead of ours, we "jump" forwards (set our count to the received
+ count). However, we must then suppress our next count increment. This
+ is a very simple form of "time" synchronization (for an abstract
+ notion of time). */
+ bool suppress_count_change;
+
+ //
+ // On bootup, initialize radio and serial communications, and our
+ // own state variables.
+ //
+ event void Boot.booted() {
+ local.interval = DEFAULT_INTERVAL;
+ local.id = TOS_NODE_ID;
+ local.version = 0;
+
+ // Beginning our initialization phases:
+ if (call RadioControl.start() != SUCCESS)
+ fatal_problem();
+
+ if (call RoutingControl.start() != SUCCESS)
+ fatal_problem();
+ }
+
+ event void RadioControl.startDone(error_t error) {
+ if (error != SUCCESS)
+ fatal_problem();
+
+ if (sizeof(local) > call Send.maxPayloadLength())
+ fatal_problem();
+
+ if (call SerialControl.start() != SUCCESS)
+ fatal_problem();
+ }
+
+ event void SerialControl.startDone(error_t error) {
+ if (error != SUCCESS)
+ fatal_problem();
+
+ // This is how to set yourself as a root to the collection layer:
+ if (local.id % 500 == 0)
+ call RootControl.setRoot();
+
+ startTimer();
+ }
+
+ static void startTimer() {
+ if (call Timer.isRunning()) call Timer.stop();
+ call Timer.startPeriodic(local.interval);
+ reading = 0;
+ }
+
+ event void RadioControl.stopDone(error_t error) { }
+ event void SerialControl.stopDone(error_t error) { }
+
+ //
+ // Only the root will receive messages from this interface; its job
+ // is to forward them to the serial uart for processing on the pc
+ // connected to the sensor network.
+ //
+ event message_t*
+ Receive.receive(message_t* msg, void *payload, uint8_t len) {
+ oscilloscope_t* in = (oscilloscope_t*)payload;
+ oscilloscope_t* out;
+ call Leds.led1Toggle();
+ if (uartbusy == FALSE) {
+ out = (oscilloscope_t*)call SerialSend.getPayload(&uartbuf);
+ if (len != sizeof(oscilloscope_t)) {
+ return msg;
+ }
+ else {
+ memcpy(out, in, sizeof(oscilloscope_t));
+ }
+ uartlen = sizeof(oscilloscope_t);
+ post uartSendTask();
+ } else {
+ // The UART is busy; queue up messages and service them when the
+ // UART becomes free.
+ message_t *newmsg = call UARTMessagePool.get();
+ if (newmsg == NULL) {
+ // drop the message on the floor if we run out of queue space.
+ report_problem();
+ return msg;
+ }
+
+ //Prepare message to be sent over the uart
+ out = (oscilloscope_t*)call SerialSend.getPayload(newmsg);
+ memcpy(out, in, sizeof(oscilloscope_t));
+
+ if (call UARTQueue.enqueue(newmsg) != SUCCESS) {
+ // drop the message on the floor and hang if we run out of
+ // queue space without running out of queue space first (this
+ // should not occur).
+ call UARTMessagePool.put(newmsg);
+ fatal_problem();
+ return msg;
+ }
+ }
+
+ return msg;
+ }
+
+ task void uartSendTask() {
+ if (call SerialSend.send(0xffff, &uartbuf, uartlen) != SUCCESS) {
+ report_problem();
+ } else {
+ uartbusy = TRUE;
+ }
+ }
+
+ event void SerialSend.sendDone(message_t *msg, error_t error) {
+ uartbusy = FALSE;
+ if (call UARTQueue.empty() == FALSE) {
+ // We just finished a UART send, and the uart queue is
+ // non-empty. Let's start a new one.
+ message_t *queuemsg = call UARTQueue.dequeue();
+ if (queuemsg == NULL) {
+ fatal_problem();
+ return;
+ }
+ memcpy(&uartbuf, queuemsg, sizeof(message_t));
+ if (call UARTMessagePool.put(queuemsg) != SUCCESS) {
+ fatal_problem();
+ return;
+ }
+ post uartSendTask();
+ }
+ }
+
+ //
+ // Overhearing other traffic in the network.
+ //
+ event message_t*
+ Snoop.receive(message_t* msg, void* payload, uint8_t len) {
+ oscilloscope_t *omsg = payload;
+
+ report_received();
+
+ // If we receive a newer version, update our interval.
+ if (omsg->version > local.version) {
+ local.version = omsg->version;
+ local.interval = omsg->interval;
+ startTimer();
+ }
+
+ // If we hear from a future count, jump ahead but suppress our own
+ // change.
+ if (omsg->count > local.count) {
+ local.count = omsg->count;
+ suppress_count_change = TRUE;
+ }
+
+ return msg;
+ }
+
+ /* At each sample period:
+ - if local sample buffer is full, send accumulated samples
+ - read next sample
+ */
+ event void Timer.fired() {
+ if (TOS_NODE_ID % 500 == 0) {return;}
+ if (reading == NREADINGS) {
+ if (!sendbusy) {
+ oscilloscope_t *o = (oscilloscope_t *)call Send.getPayload(&sendbuf);
+ memcpy(o, &local, sizeof(local));
+ if (call Send.send(&sendbuf, sizeof(local)) == SUCCESS)
+ sendbusy = TRUE;
+ else
+ report_problem();
+ }
+
+ reading = 0;
+ /* Part 2 of cheap "time sync": increment our count if we didn't
+ jump ahead. */
+ if (!suppress_count_change)
+ local.count++;
+ suppress_count_change = FALSE;
+ }
+
+ if (call Read.read() != SUCCESS)
+ fatal_problem();
+ }
+
+ event void Send.sendDone(message_t* msg, error_t error) {
+ if (error == SUCCESS)
+ report_sent();
+ else
+ report_problem();
+
+ sendbusy = FALSE;
+ }
+
+ event void Read.readDone(error_t result, uint16_t data) {
+ if (result != SUCCESS) {
+ data = 0xffff;
+ report_problem();
+ }
+ local.readings[reading++] = data;
+ }
+
+
+ // Use LEDs to report various status issues.
+ static void fatal_problem() {
+ call Leds.led0On();
+ call Leds.led1On();
+ call Leds.led2On();
+ call Timer.stop();
+ }
+
+ static void report_problem() { call Leds.led0Toggle(); }
+ static void report_sent() { call Leds.led1Toggle(); }
+ static void report_received() { call Leds.led2Toggle(); }
+}
--- /dev/null
+README for MultihopOscilloscope
+Author/Contact: tinyos-help@millennium.berkeley.edu
+
+Description:
+
+MultihopOscilloscope is a simple data-collection demo. It periodically samples
+the default sensor and broadcasts a message every few readings. These readings
+can be displayed by the Java "Oscilloscope" application found in the
+TOSROOT/apps/Oscilloscope/java subdirectory. The sampling rate starts at 4Hz,
+but can be changed from the Java application.
+
+You can compile MultihopOscilloscope with a sensor board's default sensor by
+compiling as follows:
+
+ SENSORBOARD=<sensorboard name> make <mote>
+
+You can change the sensor used by editing MultihopOscilloscopeAppC.nc.
+
+This version of MultihopOscilloscope uses the MultihopLQI collection
+layer in tos/lib/net/lqi.
+
+Tools:
+
+The Java application displays readings it receives from motes running the
+MultihopOscilloscope demo via a serial forwarder. To run it, change to the
+TOSROOT/apps/Oscilloscope/java subdirectory and type:
+
+ make
+ java net.tinyos.sf.SerialForwarder -comm serial@<serial port>:<mote>
+ # e.g., java net.tinyps.sf.SerialForwarder -comm serial@/dev/ttyUSB0:mica2
+ # or java net.tinyps.sf.SerialForwarder -comm serial@COM2:telosb
+ ./run
+
+The controls at the bootom of the screen allow yoy to zoom in or out the X
+axis, change the range of the Y axis, and clear all received data. You can
+change the color used to display a mote by clicking on its color in the
+mote table.
+
+Known bugs/limitations:
+
+None.
+
+See also:
+TEP 113: Serial Communications, TEP 119: Collection.
+
+Notes:
+
+MultihopOscilloscope configures a mote whose TOS_NODE_ID modulo 500 is zero
+to be a collection root.
--- /dev/null
+/*
+ * 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;
+ }
+}
+
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+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
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}