]> oss.titaniummirror.com Git - tinyos-2.x.git/commitdiff
LQI version of MultihopOscilloscope.
authorscipio <scipio>
Fri, 13 Apr 2007 00:10:28 +0000 (00:10 +0000)
committerscipio <scipio>
Fri, 13 Apr 2007 00:10:28 +0000 (00:10 +0000)
13 files changed:
apps/MultihopOscilloscopeLqi/Makefile [new file with mode: 0644]
apps/MultihopOscilloscopeLqi/MultihopOscilloscope.h [new file with mode: 0644]
apps/MultihopOscilloscopeLqi/MultihopOscilloscopeAppC.nc [new file with mode: 0644]
apps/MultihopOscilloscopeLqi/MultihopOscilloscopeC.nc [new file with mode: 0644]
apps/MultihopOscilloscopeLqi/README.txt [new file with mode: 0644]
apps/MultihopOscilloscopeLqi/java/ColorCellEditor.java [new file with mode: 0644]
apps/MultihopOscilloscopeLqi/java/Data.java [new file with mode: 0644]
apps/MultihopOscilloscopeLqi/java/Graph.java [new file with mode: 0644]
apps/MultihopOscilloscopeLqi/java/Makefile [new file with mode: 0644]
apps/MultihopOscilloscopeLqi/java/Node.java [new file with mode: 0644]
apps/MultihopOscilloscopeLqi/java/Oscilloscope.java [new file with mode: 0644]
apps/MultihopOscilloscopeLqi/java/Window.java [new file with mode: 0644]
apps/MultihopOscilloscopeLqi/java/oscilloscope.jar [new file with mode: 0644]

diff --git a/apps/MultihopOscilloscopeLqi/Makefile b/apps/MultihopOscilloscopeLqi/Makefile
new file mode 100644 (file)
index 0000000..64913b5
--- /dev/null
@@ -0,0 +1,4 @@
+COMPONENT=MultihopOscilloscopeAppC
+CFLAGS += -I$(TOSDIR)/lib/net/ -I$(TOSDIR)/lib/net/lqi
+
+include $(MAKERULES)
diff --git a/apps/MultihopOscilloscopeLqi/MultihopOscilloscope.h b/apps/MultihopOscilloscopeLqi/MultihopOscilloscope.h
new file mode 100644 (file)
index 0000000..5f04053
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * 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
diff --git a/apps/MultihopOscilloscopeLqi/MultihopOscilloscopeAppC.nc b/apps/MultihopOscilloscopeLqi/MultihopOscilloscopeAppC.nc
new file mode 100644 (file)
index 0000000..cefe674
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * 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, LedsC, new TimerMilliC(), 
+    new DemoSensorC() as Sensor;
+
+  //MainC.SoftwareInit -> Sensor;
+  
+  MultihopOscilloscopeC.Boot -> MainC;
+  MultihopOscilloscopeC.Timer -> TimerMilliC;
+  MultihopOscilloscopeC.Read -> Sensor;
+  MultihopOscilloscopeC.Leds -> LedsC;
+
+  //
+  // 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.Snoop -> Collector.Snoop[AM_OSCILLOSCOPE];
+  MultihopOscilloscopeC.Receive -> Collector.Receive[AM_OSCILLOSCOPE];
+  MultihopOscilloscopeC.RootControl -> Collector;
+
+  components new PoolC(message_t, 10) as UARTMessagePoolP,
+    new QueueC(message_t*, 10) as UARTQueueP;
+
+  MultihopOscilloscopeC.UARTMessagePool -> UARTMessagePoolP;
+  MultihopOscilloscopeC.UARTQueue -> UARTQueueP;
+  
+  components new PoolC(message_t, 20) as DebugMessagePool,
+    new QueueC(message_t*, 20) as DebugSendQueue,
+    new SerialAMSenderC(AM_LQI_DEBUG) as DebugSerialSender,
+    UARTDebugSenderP as DebugSender;
+
+  DebugSender.Boot -> MainC;
+  DebugSender.UARTSend -> DebugSerialSender;
+  DebugSender.MessagePool -> DebugMessagePool;
+  DebugSender.SendQueue -> DebugSendQueue;
+  Collector.CollectionDebug -> DebugSender;
+
+}
diff --git a/apps/MultihopOscilloscopeLqi/MultihopOscilloscopeC.nc b/apps/MultihopOscilloscopeLqi/MultihopOscilloscopeC.nc
new file mode 100644 (file)
index 0000000..fff54d7
--- /dev/null
@@ -0,0 +1,276 @@
+/*
+ * 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;
+    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 (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(); }
+}
diff --git a/apps/MultihopOscilloscopeLqi/README.txt b/apps/MultihopOscilloscopeLqi/README.txt
new file mode 100644 (file)
index 0000000..e186a96
--- /dev/null
@@ -0,0 +1,51 @@
+README for MultihopOscilloscopeLqi
+Author/Contact: tinyos-help@millennium.berkeley.edu
+
+Description:
+
+MultihopOscilloscope is a simple data-collection demo. This variant,
+MultihopOscilloscopeLqi, works only on platforms that have the CC2420.
+Rather than use CTP, it uses MultihopLqi (lib/net/lqi), which is 
+much lighter weight but not quite as efficient or reliable. 
+
+The application 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.
+
+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.
diff --git a/apps/MultihopOscilloscopeLqi/java/ColorCellEditor.java b/apps/MultihopOscilloscopeLqi/java/ColorCellEditor.java
new file mode 100644 (file)
index 0000000..f88b0b6
--- /dev/null
@@ -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/MultihopOscilloscopeLqi/java/Data.java b/apps/MultihopOscilloscopeLqi/java/Data.java
new file mode 100644 (file)
index 0000000..ac35aa7
--- /dev/null
@@ -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/MultihopOscilloscopeLqi/java/Graph.java b/apps/MultihopOscilloscopeLqi/java/Graph.java
new file mode 100644 (file)
index 0000000..9a42c1c
--- /dev/null
@@ -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/MultihopOscilloscopeLqi/java/Makefile b/apps/MultihopOscilloscopeLqi/java/Makefile
new file mode 100644 (file)
index 0000000..55c2605
--- /dev/null
@@ -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/MultihopOscilloscopeLqi/java/Node.java b/apps/MultihopOscilloscopeLqi/java/Node.java
new file mode 100644 (file)
index 0000000..cfe8db9
--- /dev/null
@@ -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/MultihopOscilloscopeLqi/java/Oscilloscope.java b/apps/MultihopOscilloscopeLqi/java/Oscilloscope.java
new file mode 100644 (file)
index 0000000..3db3741
--- /dev/null
@@ -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/MultihopOscilloscopeLqi/java/Window.java b/apps/MultihopOscilloscopeLqi/java/Window.java
new file mode 100644 (file)
index 0000000..d7979bf
--- /dev/null
@@ -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/MultihopOscilloscopeLqi/java/oscilloscope.jar b/apps/MultihopOscilloscopeLqi/java/oscilloscope.jar
new file mode 100644 (file)
index 0000000..28eb3b4
Binary files /dev/null and b/apps/MultihopOscilloscopeLqi/java/oscilloscope.jar differ