Moved the Collect-view application from the examples/sky-shell directory and into its own tools/collect-view directory
This commit is contained in:
parent
753df27728
commit
04fa714930
27 changed files with 68 additions and 62 deletions
60
tools/collect-view/build.xml
Normal file
60
tools/collect-view/build.xml
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<project name="Sensor Data Collect with Contiki" default="dist" basedir=".">
|
||||
<property name="java" location="src"/>
|
||||
<property name="build" location="build"/>
|
||||
<property name="lib" location="lib"/>
|
||||
<property name="dist" location="dist"/>
|
||||
<property name="contiki" location="../.."/>
|
||||
<property name="archive" value="collect-demo.jar"/>
|
||||
<property name="main" value="se.sics.contiki.collect.CollectServer"/>
|
||||
|
||||
<target name="init">
|
||||
<tstamp/>
|
||||
<mkdir dir="${build}"/>
|
||||
</target>
|
||||
|
||||
<target name="compile" depends="init">
|
||||
<javac srcdir="${java}" destdir="${build}" debug="true">
|
||||
<classpath>
|
||||
<fileset dir="${lib}">
|
||||
<include name="**/*.jar"/>
|
||||
</fileset>
|
||||
</classpath>
|
||||
</javac>
|
||||
</target>
|
||||
|
||||
<target name="dist" depends="compile">
|
||||
<mkdir dir="${dist}"/>
|
||||
<jar destfile="${dist}/${archive}" basedir="${build}">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="${main}"/>
|
||||
<attribute name="Class-Path" value="lib/jcommon-1.0.13.jar lib/jfreechart-1.0.10.jar"/>
|
||||
</manifest>
|
||||
</jar>
|
||||
<copy todir="${dist}" file="collect-init.script"/>
|
||||
<copy todir="${dist}" file="sky-shell.ihex"/>
|
||||
<mkdir dir="${dist}/lib"/>
|
||||
<copy todir="${dist}/lib">
|
||||
<fileset dir="${lib}">
|
||||
<include name="**/*.jar"/>
|
||||
</fileset>
|
||||
</copy>
|
||||
<mkdir dir="${dist}/tools"/>
|
||||
<copy todir="${dist}/tools">
|
||||
<fileset dir="${contiki}/tools/sky"/>
|
||||
</copy>
|
||||
<copy file="${contiki}/tools/cygwin/cygwin1.dll" todir="${dist}/tools"/>
|
||||
<chmod dir="${dist}/tools" perm="a+x" includes="**/*"/>
|
||||
</target>
|
||||
|
||||
<target name="run" depends="dist">
|
||||
<java fork="yes" dir="${dist}" jar="${dist}/${archive}"/>
|
||||
</target>
|
||||
|
||||
<target name="clean">
|
||||
<delete dir="${build}"/>
|
||||
<delete dir="${dist}"/>
|
||||
</target>
|
||||
|
||||
</project>
|
6
tools/collect-view/collect-init.script
Normal file
6
tools/collect-view/collect-init.script
Normal file
|
@ -0,0 +1,6 @@
|
|||
echo ~K
|
||||
echo killall
|
||||
sleep 2
|
||||
echo mac 0
|
||||
sleep 2
|
||||
echo time %TIME% | null
|
BIN
tools/collect-view/lib/jcommon-1.0.13.jar
Normal file
BIN
tools/collect-view/lib/jcommon-1.0.13.jar
Normal file
Binary file not shown.
BIN
tools/collect-view/lib/jfreechart-1.0.10.jar
Normal file
BIN
tools/collect-view/lib/jfreechart-1.0.10.jar
Normal file
Binary file not shown.
1509
tools/collect-view/src/se/sics/contiki/collect/CollectServer.java
Normal file
1509
tools/collect-view/src/se/sics/contiki/collect/CollectServer.java
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* Copyright (c) 2010, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: CommandConnection.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* CommandConnection
|
||||
*
|
||||
* Authors : Joakim Eriksson, Niclas Finne
|
||||
* Created : 5 oct 2010
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package se.sics.contiki.collect;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class CommandConnection extends SerialConnection {
|
||||
|
||||
protected Process commandProcess;
|
||||
protected String command;
|
||||
|
||||
public CommandConnection(SerialConnectionListener listener) {
|
||||
super(listener);
|
||||
}
|
||||
|
||||
public CommandConnection(SerialConnectionListener listener, String command) {
|
||||
super(listener);
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConnectionName() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public String getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public void setCommand(String command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open(String comPort) {
|
||||
close();
|
||||
this.comPort = comPort == null ? "" : comPort;
|
||||
|
||||
String fullCommand = getCommand();
|
||||
|
||||
isClosed = false;
|
||||
try {
|
||||
String[] cmd = fullCommand.split(" ");
|
||||
System.err.println("Running '" + fullCommand + '\'');
|
||||
|
||||
commandProcess = Runtime.getRuntime().exec(cmd);
|
||||
final BufferedReader input = new BufferedReader(new InputStreamReader(commandProcess.getInputStream()));
|
||||
final BufferedReader err = new BufferedReader(new InputStreamReader(commandProcess.getErrorStream()));
|
||||
setSerialOutput(new PrintWriter(new OutputStreamWriter(commandProcess.getOutputStream())));
|
||||
|
||||
/* Start thread listening on standard out */
|
||||
Thread readInput = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
String line;
|
||||
try {
|
||||
while ((line = input.readLine()) != null) {
|
||||
standardData(line);
|
||||
}
|
||||
input.close();
|
||||
System.err.println("SerialConnection command terminated.");
|
||||
closeConnection();
|
||||
} catch (IOException e) {
|
||||
lastError = "Error when reading from SerialConnection command: " + e;
|
||||
System.err.println(lastError);
|
||||
if (!isClosed) {
|
||||
e.printStackTrace();
|
||||
closeConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, "read input stream thread");
|
||||
|
||||
/* Start thread listening on standard err */
|
||||
Thread readError = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
String line;
|
||||
try {
|
||||
while ((line = err.readLine()) != null) {
|
||||
errorData(line);
|
||||
}
|
||||
err.close();
|
||||
} catch (IOException e) {
|
||||
if (!isClosed) {
|
||||
System.err.println("Error when reading from SerialConnection command: " + e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, "read error stream thread");
|
||||
|
||||
readInput.start();
|
||||
readError.start();
|
||||
} catch (Exception e) {
|
||||
lastError = "Failed to execute '" + fullCommand + "': " + e;
|
||||
System.err.println(lastError);
|
||||
e.printStackTrace();
|
||||
closeConnection();
|
||||
}
|
||||
}
|
||||
|
||||
protected void standardData(String line) {
|
||||
if (!isOpen) {
|
||||
isOpen = true;
|
||||
serialOpened();
|
||||
}
|
||||
serialData(line);
|
||||
}
|
||||
|
||||
protected void errorData(String line) {
|
||||
System.err.println("SerialConnection error stream> " + line);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doClose() {
|
||||
if (commandProcess != null) {
|
||||
commandProcess.destroy();
|
||||
commandProcess = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2010, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: Configurable.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* Configurable
|
||||
*
|
||||
* Authors : Joakim Eriksson, Niclas Finne
|
||||
* Created : 24 sep 2010
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package se.sics.contiki.collect;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public interface Configurable {
|
||||
|
||||
public void updateConfig(Properties config);
|
||||
|
||||
}
|
87
tools/collect-view/src/se/sics/contiki/collect/Link.java
Normal file
87
tools/collect-view/src/se/sics/contiki/collect/Link.java
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright (c) 2008, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: Link.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* Link
|
||||
*
|
||||
* Authors : Joakim Eriksson, Niclas Finne
|
||||
* Created : 3 jul 2008
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package se.sics.contiki.collect;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class Link {
|
||||
|
||||
public final Node node;
|
||||
|
||||
private double etx;
|
||||
private int quality = 100;
|
||||
private long lastActive = 0L;
|
||||
|
||||
public Link(Node node) {
|
||||
this.node = node;
|
||||
this.lastActive = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public Node getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
public int getQuality() {
|
||||
return quality;
|
||||
}
|
||||
|
||||
public void setQuality(int quality) {
|
||||
this.quality = quality;
|
||||
}
|
||||
|
||||
public double getETX() {
|
||||
return etx;
|
||||
}
|
||||
|
||||
public void setETX(double etx) {
|
||||
this.etx = etx;
|
||||
}
|
||||
|
||||
public long getLastActive() {
|
||||
return lastActive;
|
||||
}
|
||||
|
||||
public void setLastActive(long time) {
|
||||
this.lastActive = time;
|
||||
}
|
||||
|
||||
}
|
222
tools/collect-view/src/se/sics/contiki/collect/MoteFinder.java
Normal file
222
tools/collect-view/src/se/sics/contiki/collect/MoteFinder.java
Normal file
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* Copyright (c) 2008, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: MoteFinder.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* Motelist
|
||||
*
|
||||
* Authors : Joakim Eriksson, Niclas Finne
|
||||
* Created : 4 jul 2008
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package se.sics.contiki.collect;
|
||||
import java.awt.Component;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class MoteFinder {
|
||||
|
||||
public static final String MOTELIST_WINDOWS = "./tools/motelist-windows.exe";
|
||||
public static final String MOTELIST_LINUX = "./tools/motelist-linux";
|
||||
|
||||
private final Pattern motePattern;
|
||||
private final boolean isWindows;
|
||||
private Process moteListProcess;
|
||||
// private boolean hasVerifiedProcess;
|
||||
private ArrayList<String> comList = new ArrayList<String>();
|
||||
private ArrayList<String> moteList = new ArrayList<String>();
|
||||
|
||||
public MoteFinder() {
|
||||
String osName = System.getProperty("os.name", "").toLowerCase();
|
||||
isWindows = osName.startsWith("win");
|
||||
motePattern = Pattern.compile("\\s(COM|/dev/[a-zA-Z]+)(\\d+)\\s");
|
||||
}
|
||||
|
||||
public String[] getMotes() throws IOException {
|
||||
searchForMotes();
|
||||
return getMoteList();
|
||||
}
|
||||
|
||||
public String[] getComPorts() throws IOException {
|
||||
searchForMotes();
|
||||
return getComList();
|
||||
}
|
||||
|
||||
private void searchForMotes() throws IOException {
|
||||
comList.clear();
|
||||
moteList.clear();
|
||||
// hasVerifiedProcess = false;
|
||||
|
||||
/* Connect to COM using external serialdump application */
|
||||
String fullCommand;
|
||||
if (isWindows) {
|
||||
fullCommand = MOTELIST_WINDOWS;
|
||||
} else {
|
||||
fullCommand = MOTELIST_LINUX;
|
||||
}
|
||||
|
||||
try {
|
||||
String[] cmd = new String[] { fullCommand };
|
||||
moteListProcess = Runtime.getRuntime().exec(cmd);
|
||||
final BufferedReader input = new BufferedReader(new InputStreamReader(moteListProcess.getInputStream()));
|
||||
final BufferedReader err = new BufferedReader(new InputStreamReader(moteListProcess.getErrorStream()));
|
||||
|
||||
/* Start thread listening on stdout */
|
||||
Thread readInput = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
String line;
|
||||
try {
|
||||
while ((line = input.readLine()) != null) {
|
||||
parseIncomingLine(line);
|
||||
}
|
||||
input.close();
|
||||
} catch (IOException e) {
|
||||
System.err.println("Exception when reading from motelist");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}, "read motelist thread");
|
||||
|
||||
/* Start thread listening on stderr */
|
||||
Thread readError = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
String line;
|
||||
try {
|
||||
while ((line = err.readLine()) != null) {
|
||||
System.err.println("Motelist error stream> " + line);
|
||||
}
|
||||
err.close();
|
||||
} catch (IOException e) {
|
||||
System.err.println("Exception when reading from motelist error stream: " + e);
|
||||
}
|
||||
}
|
||||
}, "read motelist error stream thread");
|
||||
|
||||
readInput.start();
|
||||
readError.start();
|
||||
|
||||
// Wait for the motelist program to finish executing
|
||||
readInput.join();
|
||||
} catch (Exception e) {
|
||||
throw (IOException) new IOException("Failed to execute '" + fullCommand + "'").initCause(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String[] getComList() {
|
||||
return comList.toArray(new String[comList.size()]);
|
||||
}
|
||||
|
||||
private String[] getMoteList() {
|
||||
return moteList.toArray(new String[moteList.size()]);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (moteListProcess != null) {
|
||||
moteListProcess.destroy();
|
||||
moteListProcess = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void parseIncomingLine(String line) {
|
||||
if (line.contains("No devices found") || line.startsWith("Reference")) {
|
||||
// No Sky connected or title before connected motes
|
||||
// hasVerifiedProcess = true;
|
||||
} else if (line.startsWith("-------")) {
|
||||
// Separator
|
||||
} else {
|
||||
Matcher matcher = motePattern.matcher(line);
|
||||
if (matcher.find()) {
|
||||
String dev = matcher.group(1);
|
||||
String no = matcher.group(2);
|
||||
String comPort = dev + no;
|
||||
String moteID = comPort;
|
||||
if (isWindows) {
|
||||
// Special handling of mote id under Windows
|
||||
int moteNumber = Integer.parseInt(no);
|
||||
moteID = Integer.toString(moteNumber - 1);
|
||||
}
|
||||
comList.add(comPort);
|
||||
moteList.add(moteID);
|
||||
} else {
|
||||
System.err.println("Motelist> " + line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String selectComPort(Component parent) {
|
||||
MoteFinder finder = new MoteFinder();
|
||||
try {
|
||||
String[] motes = finder.getComPorts();
|
||||
if (motes == null || motes.length == 0) {
|
||||
JOptionPane.showMessageDialog(parent, "Could not find any connected motes.", "No mote found", JOptionPane.ERROR_MESSAGE);
|
||||
return null;
|
||||
} else if (motes.length == 1) {
|
||||
// Only one node found
|
||||
return motes[0];
|
||||
} else {
|
||||
// Several motes found
|
||||
return (String) JOptionPane.showInputDialog(
|
||||
parent, "Found multiple connected motes. Please select serial port:",
|
||||
"Select serial port", JOptionPane.QUESTION_MESSAGE, null, motes, motes[0]);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
JOptionPane.showMessageDialog(parent, "Failed to search for connected motes:\n" + e, "Error", JOptionPane.ERROR_MESSAGE);
|
||||
return null;
|
||||
} finally {
|
||||
finder.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
MoteFinder finder = new MoteFinder();
|
||||
String[] comPorts = args.length > 0 && "-v".equals(args[0]) ?
|
||||
finder.getMotes() : finder.getComPorts();
|
||||
finder.close();
|
||||
if (comPorts == null || comPorts.length == 0) {
|
||||
System.out.println("No motes connected");
|
||||
} else {
|
||||
for(String port: comPorts) {
|
||||
System.out.println("Found Sky at " + port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,281 @@
|
|||
/*
|
||||
* Copyright (c) 2008, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: MoteProgrammer.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* MoteProgrammer
|
||||
*
|
||||
* Authors : Joakim Eriksson, Niclas Finne
|
||||
* Created : 10 jul 2008
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package se.sics.contiki.collect;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JProgressBar;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class MoteProgrammer {
|
||||
|
||||
private MoteProgrammerProcess[] processes;
|
||||
private String[] motes;
|
||||
private String firmwareFile;
|
||||
|
||||
private Window parent;
|
||||
private JProgressBar progressBar;
|
||||
protected JTextArea logTextArea;
|
||||
protected JDialog dialog;
|
||||
protected JButton closeButton;
|
||||
private boolean isDone;
|
||||
|
||||
public MoteProgrammer() {
|
||||
}
|
||||
|
||||
public Window getParentComponent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public void setParentComponent(Window parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public boolean hasMotes() {
|
||||
return motes != null && motes.length > 0;
|
||||
}
|
||||
|
||||
public String[] getMotes() {
|
||||
return motes;
|
||||
}
|
||||
|
||||
public void setMotes(String[] motes) {
|
||||
this.motes = motes;
|
||||
}
|
||||
|
||||
public void searchForMotes() throws IOException {
|
||||
MoteFinder finder = new MoteFinder();
|
||||
motes = finder.getMotes();
|
||||
finder.close();
|
||||
}
|
||||
|
||||
public String getFirmwareFile() {
|
||||
return firmwareFile;
|
||||
}
|
||||
|
||||
public void setFirmwareFile(String firmwareFile) {
|
||||
this.firmwareFile = firmwareFile;
|
||||
}
|
||||
|
||||
public void programMotes() throws IOException {
|
||||
if (firmwareFile == null) {
|
||||
throw new IllegalStateException("no firmware");
|
||||
}
|
||||
if (!hasMotes()) {
|
||||
throw new IllegalStateException("no motes");
|
||||
}
|
||||
File fp = new File(firmwareFile);
|
||||
if (!fp.canRead()) {
|
||||
throw new IllegalStateException("can not read firmware file '" + fp.getAbsolutePath() + '\'');
|
||||
}
|
||||
if (parent != null) {
|
||||
// Use GUI
|
||||
dialog = new JDialog(parent, "Mote Programmer");
|
||||
progressBar = new JProgressBar(0, 100);
|
||||
progressBar.setValue(0);
|
||||
progressBar.setString("Programming...");
|
||||
progressBar.setStringPainted(true);
|
||||
progressBar.setIndeterminate(true);
|
||||
dialog.getContentPane().add(progressBar, BorderLayout.NORTH);
|
||||
|
||||
logTextArea = new JTextArea(28, 80);
|
||||
logTextArea.setEditable(false);
|
||||
logTextArea.setLineWrap(true);
|
||||
dialog.getContentPane().add(new JScrollPane(logTextArea), BorderLayout.CENTER);
|
||||
JPanel panel = new JPanel();
|
||||
closeButton = new JButton("Cancel");
|
||||
closeButton.addActionListener(new ActionListener() {
|
||||
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
MoteProgrammer.this.close();
|
||||
}
|
||||
|
||||
});
|
||||
panel.add(closeButton);
|
||||
dialog.getContentPane().add(panel, BorderLayout.SOUTH);
|
||||
dialog.pack();
|
||||
dialog.setLocationRelativeTo(parent);
|
||||
dialog.setVisible(true);
|
||||
}
|
||||
processes = new MoteProgrammerProcess[motes.length];
|
||||
isDone = false;
|
||||
try {
|
||||
log("Programming " + motes.length + " motes with '" + firmwareFile + '\'', null);
|
||||
for (int i = 0, n = processes.length; i < n; i++) {
|
||||
processes[i] = new MoteProgrammerProcess(motes[i], firmwareFile) {
|
||||
protected void logLine(String line, boolean stderr, Throwable e) {
|
||||
if (!handleLogLine(this, line, stderr, e)) {
|
||||
super.logLine(line, stderr, e);
|
||||
}
|
||||
}
|
||||
protected void processEnded() {
|
||||
handleProcessEnded(this);
|
||||
}
|
||||
};
|
||||
processes[i].start();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw (IOException) new IOException("Failed to program motes").initCause(e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void waitForProcess() throws InterruptedException {
|
||||
while (!isDone) {
|
||||
wait();
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
MoteProgrammerProcess[] processes = this.processes;
|
||||
if (processes != null) {
|
||||
this.processes = null;
|
||||
for (int i = 0, n = processes.length; i < n; i++) {
|
||||
if (processes[i] != null) {
|
||||
processes[i].stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dialog != null) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
dialog.setVisible(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
isDone = true;
|
||||
synchronized (this) {
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleProcessEnded(MoteProgrammerProcess process) {
|
||||
// Another process has finished
|
||||
log("Mote@" + process.getMoteID() + "> finished" + (process.hasError() ? " with errors": ""), null);
|
||||
MoteProgrammerProcess[] processes = this.processes;
|
||||
if (processes != null) {
|
||||
int running = 0;
|
||||
int errors = 0;
|
||||
for(MoteProgrammerProcess p: processes) {
|
||||
if (p.isRunning()) {
|
||||
running++;
|
||||
} else if (p.hasError()) {
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
if (running == 0) {
|
||||
// All processes has finished
|
||||
isDone = true;
|
||||
final String doneMessage = "Programming finished with " + errors + " errors.";
|
||||
log(doneMessage, null);
|
||||
if (closeButton != null) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
progressBar.setValue(100);
|
||||
progressBar.setIndeterminate(false);
|
||||
progressBar.setString(doneMessage);
|
||||
closeButton.setText("Close");
|
||||
}});
|
||||
}
|
||||
synchronized (this) {
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean handleLogLine(MoteProgrammerProcess moteProgrammerProcess,
|
||||
String line, boolean stderr, final Throwable e) {
|
||||
log("Mote@" + moteProgrammerProcess.getMoteID() + "> " + line, e);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void log(String line, final Throwable e) {
|
||||
System.err.println(line);
|
||||
if (e != null) {
|
||||
e.printStackTrace();
|
||||
line += "\n " + e;
|
||||
}
|
||||
final String text = line;
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
int len = logTextArea.getDocument().getLength();
|
||||
if (len == 0) {
|
||||
logTextArea.append(text);
|
||||
} else {
|
||||
logTextArea.append('\n' + text);
|
||||
len++;
|
||||
}
|
||||
logTextArea.setCaretPosition(len + text.length());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
MoteProgrammer mp = new MoteProgrammer();
|
||||
if (args.length < 1 || args.length > 2) {
|
||||
System.err.println("Usage: MoteProgrammer <firmware> [mote]");
|
||||
System.exit(1);
|
||||
}
|
||||
mp.setFirmwareFile(args[0]);
|
||||
if (args.length == 2) {
|
||||
mp.setMotes(new String[] { args[1] });
|
||||
} else {
|
||||
mp.searchForMotes();
|
||||
}
|
||||
if (!mp.hasMotes()) {
|
||||
System.err.println("No motes connected");
|
||||
System.exit(1);
|
||||
}
|
||||
mp.programMotes();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* Copyright (c) 2008, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: MoteProgrammerProcess.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* MoteProgrammerProcess
|
||||
*
|
||||
* Authors : Joakim Eriksson, Niclas Finne
|
||||
* Created : 10 jul 2008
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package se.sics.contiki.collect;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class MoteProgrammerProcess {
|
||||
|
||||
public static final String BSL_WINDOWS = "./tools/msp430-bsl-windows.exe";
|
||||
public static final String BSL_LINUX = "./tools/msp430-bsl-linux";
|
||||
|
||||
private final String moteID;
|
||||
private final String firmwareFile;
|
||||
private final String[][] commandSet;
|
||||
private int retry = 3;
|
||||
|
||||
private Process currentProcess;
|
||||
private Thread commandThread;
|
||||
private boolean isRunning;
|
||||
private boolean hasError;
|
||||
|
||||
public MoteProgrammerProcess(String moteID, String firmwareFile) {
|
||||
this.moteID = moteID;
|
||||
this.firmwareFile = firmwareFile;
|
||||
String osName = System.getProperty("os.name").toLowerCase();
|
||||
String bslCommand;
|
||||
if (osName.startsWith("win")) {
|
||||
bslCommand = BSL_WINDOWS;
|
||||
} else {
|
||||
bslCommand = BSL_LINUX;
|
||||
}
|
||||
commandSet = new String[][] {
|
||||
{ bslCommand, "--telosb", "-c", moteID, "-e" },
|
||||
{ bslCommand, "--telosb", "-c", moteID, "-I", "-p", firmwareFile },
|
||||
{ bslCommand, "--telosb", "-c", moteID, "-r" }
|
||||
};
|
||||
}
|
||||
|
||||
public String getMoteID() {
|
||||
return moteID;
|
||||
}
|
||||
|
||||
public String getFirmwareFile() {
|
||||
return firmwareFile;
|
||||
}
|
||||
|
||||
public int getRetry() {
|
||||
return retry;
|
||||
}
|
||||
|
||||
public void setRetry(int retry) {
|
||||
this.retry = retry;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return isRunning;
|
||||
}
|
||||
|
||||
public boolean hasError() {
|
||||
return hasError;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (isRunning) {
|
||||
// Already running
|
||||
return;
|
||||
}
|
||||
isRunning = true;
|
||||
commandThread = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
int count = 0;
|
||||
do {
|
||||
if (count > 0) {
|
||||
logLine("An error occurred. Retrying.", true, null);
|
||||
}
|
||||
count++;
|
||||
hasError = false;
|
||||
for (int j = 0, m = commandSet.length; j < m && isRunning && !hasError; j++) {
|
||||
runCommand(commandSet[j]);
|
||||
Thread.sleep(2000);
|
||||
}
|
||||
} while (isRunning && hasError && count < retry);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
isRunning = false;
|
||||
processEnded();
|
||||
}
|
||||
}
|
||||
});
|
||||
commandThread.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
isRunning = false;
|
||||
Process process = currentProcess;
|
||||
if (process != null) {
|
||||
process.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
public void waitForProcess() throws InterruptedException {
|
||||
if (isRunning && commandThread != null) {
|
||||
commandThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
protected void processEnded() {
|
||||
}
|
||||
|
||||
private void runCommand(String[] cmd) throws IOException, InterruptedException {
|
||||
if (currentProcess != null) {
|
||||
currentProcess.destroy();
|
||||
}
|
||||
currentProcess = Runtime.getRuntime().exec(cmd);
|
||||
final BufferedReader input = new BufferedReader(new InputStreamReader(currentProcess.getInputStream()));
|
||||
final BufferedReader err = new BufferedReader(new InputStreamReader(currentProcess.getErrorStream()));
|
||||
|
||||
/* Start thread listening on stdout */
|
||||
Thread readInput = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
String line;
|
||||
try {
|
||||
while ((line = input.readLine()) != null) {
|
||||
handleLine(line, false);
|
||||
}
|
||||
input.close();
|
||||
} catch (IOException e) {
|
||||
logLine("Error reading from command", false, e);
|
||||
}
|
||||
}
|
||||
}, "read stdout thread");
|
||||
|
||||
/* Start thread listening on stderr */
|
||||
Thread readError = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
String line;
|
||||
try {
|
||||
while ((line = err.readLine()) != null) {
|
||||
handleLine(line, true);
|
||||
}
|
||||
err.close();
|
||||
} catch (IOException e) {
|
||||
logLine("Error reading from command", true, e);
|
||||
}
|
||||
}
|
||||
}, "read stderr thread");
|
||||
|
||||
readInput.start();
|
||||
readError.start();
|
||||
|
||||
// Wait for the bsl program to finish executing
|
||||
readInput.join();
|
||||
currentProcess = null;
|
||||
}
|
||||
|
||||
private void handleLine(String line, boolean stderr) {
|
||||
if (line.toLowerCase().contains("error")) {
|
||||
hasError = true;
|
||||
}
|
||||
logLine(line, stderr, null);
|
||||
}
|
||||
|
||||
protected void logLine(String line, boolean stderr, Throwable e) {
|
||||
if (stderr) {
|
||||
System.err.println("Programmer@" + moteID + "> " + line);
|
||||
} else {
|
||||
System.out.println("Programmer@" + moteID + "> " + line);
|
||||
}
|
||||
if (e != null) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
protected String toString(String[] cmd) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0, n = cmd.length; i < n; i++) {
|
||||
if (i > 0) sb.append(' ');
|
||||
sb.append(cmd[i]);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
218
tools/collect-view/src/se/sics/contiki/collect/Node.java
Normal file
218
tools/collect-view/src/se/sics/contiki/collect/Node.java
Normal file
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* Copyright (c) 2008, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: Node.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* Node
|
||||
*
|
||||
* Authors : Joakim Eriksson, Niclas Finne
|
||||
* Created : 3 jul 2008
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package se.sics.contiki.collect;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Hashtable;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class Node implements Comparable<Node> {
|
||||
|
||||
private static final boolean SINGLE_LINK = true;
|
||||
|
||||
private SensorDataAggregator sensorDataAggregator;
|
||||
private ArrayList<SensorData> sensorDataList = new ArrayList<SensorData>();
|
||||
private ArrayList<Link> links = new ArrayList<Link>();
|
||||
|
||||
private final String id;
|
||||
private final String name;
|
||||
|
||||
private Hashtable<String,Object> objectTable;
|
||||
|
||||
private long lastActive;
|
||||
|
||||
public Node(String nodeID) {
|
||||
this(nodeID, nodeID);
|
||||
}
|
||||
|
||||
public Node(String nodeID, String nodeName) {
|
||||
this.id = nodeID;
|
||||
this.name = nodeName;
|
||||
sensorDataAggregator = new SensorDataAggregator(this);
|
||||
}
|
||||
|
||||
public final String getID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public final String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public long getLastActive() {
|
||||
return lastActive;
|
||||
}
|
||||
|
||||
public void setLastActive(long lastActive) {
|
||||
this.lastActive = lastActive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Node o) {
|
||||
String i1 = id;
|
||||
String i2 = o.getID();
|
||||
// Shorter id first (4.0 before 10.0)
|
||||
if (i1.length() == i2.length()) {
|
||||
return i1.compareTo(i2);
|
||||
}
|
||||
return i1.length() - i2.length();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Attributes
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
public Object getAttribute(String key) {
|
||||
return getAttribute(key, null);
|
||||
}
|
||||
|
||||
public Object getAttribute(String key, Object defaultValue) {
|
||||
if (objectTable == null) {
|
||||
return null;
|
||||
}
|
||||
Object val = objectTable.get(key);
|
||||
return val == null ? defaultValue : val;
|
||||
}
|
||||
|
||||
public void setAttribute(String key, Object value) {
|
||||
if (objectTable == null) {
|
||||
objectTable = new Hashtable<String,Object>();
|
||||
}
|
||||
objectTable.put(key, value);
|
||||
}
|
||||
|
||||
public void clearAttributes() {
|
||||
if (objectTable != null) {
|
||||
objectTable.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// SensorData
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
public SensorDataAggregator getSensorDataAggregator() {
|
||||
return sensorDataAggregator;
|
||||
}
|
||||
|
||||
public SensorData[] getAllSensorData() {
|
||||
return sensorDataList.toArray(new SensorData[sensorDataList.size()]);
|
||||
}
|
||||
|
||||
public void removeAllSensorData() {
|
||||
sensorDataList.clear();
|
||||
sensorDataAggregator.clear();
|
||||
}
|
||||
|
||||
public SensorData getSensorData(int index) {
|
||||
return sensorDataList.get(index);
|
||||
}
|
||||
|
||||
public int getSensorDataCount() {
|
||||
return sensorDataList.size();
|
||||
}
|
||||
|
||||
public boolean addSensorData(SensorData data) {
|
||||
if (sensorDataList.size() > 0) {
|
||||
SensorData last = sensorDataList.get(sensorDataList.size() - 1);
|
||||
if (data.getNodeTime() < last.getNodeTime()) {
|
||||
// Sensor data already added
|
||||
System.out.println("SensorData: ignoring (time " + (data.getNodeTime() - last.getNodeTime())
|
||||
+ "msec): " + data);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
sensorDataList.add(data);
|
||||
sensorDataAggregator.addSensorData(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Links
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
public Link getLink(Node node) {
|
||||
for(Link l: links) {
|
||||
if (l.node == node) {
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
// Add new link
|
||||
Link l = new Link(node);
|
||||
if (SINGLE_LINK) {
|
||||
links.clear();
|
||||
}
|
||||
links.add(l);
|
||||
return l;
|
||||
}
|
||||
|
||||
public Link getLink(int index) {
|
||||
return links.get(index);
|
||||
}
|
||||
|
||||
public int getLinkCount() {
|
||||
return links.size();
|
||||
}
|
||||
|
||||
public void removeLink(Node node) {
|
||||
for (int i = 0, n = links.size(); i < n; i++) {
|
||||
Link l = links.get(i);
|
||||
if (l.node == node) {
|
||||
links.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clearLinks() {
|
||||
links.clear();
|
||||
}
|
||||
|
||||
}
|
244
tools/collect-view/src/se/sics/contiki/collect/SensorData.java
Normal file
244
tools/collect-view/src/se/sics/contiki/collect/SensorData.java
Normal file
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
* Copyright (c) 2008, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: SensorData.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* SensorData
|
||||
*
|
||||
* Authors : Joakim Eriksson, Niclas Finne
|
||||
* Created : 3 jul 2008
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package se.sics.contiki.collect;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class SensorData implements SensorInfo {
|
||||
|
||||
private final Node node;
|
||||
private final int[] values;
|
||||
private final long nodeTime;
|
||||
private final long systemTime;
|
||||
private int seqno;
|
||||
private boolean isDuplicate;
|
||||
|
||||
public SensorData(Node node, int[] values, long systemTime) {
|
||||
this.node = node;
|
||||
this.values = values;
|
||||
this.nodeTime = ((values[TIMESTAMP1] << 16) + values[TIMESTAMP2]) * 1000L;
|
||||
this.systemTime = systemTime;
|
||||
this.seqno = values[SEQNO];
|
||||
}
|
||||
|
||||
public Node getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
public String getNodeID() {
|
||||
return node.getID();
|
||||
}
|
||||
|
||||
public boolean isDuplicate() {
|
||||
return isDuplicate;
|
||||
}
|
||||
|
||||
public void setDuplicate(boolean isDuplicate) {
|
||||
this.isDuplicate = isDuplicate;
|
||||
}
|
||||
|
||||
public int getSeqno() {
|
||||
return seqno;
|
||||
}
|
||||
|
||||
public void setSeqno(int seqno) {
|
||||
this.seqno = seqno;
|
||||
}
|
||||
|
||||
public int getValue(int index) {
|
||||
return values[index];
|
||||
}
|
||||
|
||||
public int getValueCount() {
|
||||
return values.length;
|
||||
}
|
||||
|
||||
public long getNodeTime() {
|
||||
return nodeTime;
|
||||
}
|
||||
|
||||
public long getSystemTime() {
|
||||
return systemTime;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (systemTime > 0L) {
|
||||
sb.append(systemTime).append(' ');
|
||||
}
|
||||
for (int i = 0, n = values.length; i < n; i++) {
|
||||
if (i > 0) sb.append(' ');
|
||||
sb.append(values[i]);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static SensorData parseSensorData(CollectServer server, String line) {
|
||||
return parseSensorData(server, line, 0);
|
||||
}
|
||||
|
||||
public static SensorData parseSensorData(CollectServer server, String line, long systemTime) {
|
||||
String[] components = line.trim().split("[ \t]+");
|
||||
// Check if COOJA log
|
||||
if (components.length == VALUES_COUNT + 2 && components[1].startsWith("ID:")) {
|
||||
if (!components[2].equals("" + VALUES_COUNT)) {
|
||||
// Ignore non sensor data
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
systemTime = Long.parseLong(components[0]);
|
||||
components = Arrays.copyOfRange(components, 2, components.length);
|
||||
} catch (NumberFormatException e) {
|
||||
// First column does not seem to be system time
|
||||
}
|
||||
|
||||
} else if (components[0].length() > 8) {
|
||||
// Sensor data prefixed with system time
|
||||
try {
|
||||
systemTime = Long.parseLong(components[0]);
|
||||
components = Arrays.copyOfRange(components, 1, components.length);
|
||||
} catch (NumberFormatException e) {
|
||||
// First column does not seem to be system time
|
||||
}
|
||||
}
|
||||
if (components.length != SensorData.VALUES_COUNT) {
|
||||
return null;
|
||||
}
|
||||
// Sensor data line (probably)
|
||||
int[] data = parseToInt(components);
|
||||
if (data == null || data[0] != VALUES_COUNT) {
|
||||
System.err.println("Failed to parse data line: '" + line + "'");
|
||||
return null;
|
||||
}
|
||||
String nodeID = mapNodeID(data[NODE_ID]);
|
||||
Node node = server.addNode(nodeID);
|
||||
return new SensorData(node, data, systemTime);
|
||||
}
|
||||
|
||||
public static String mapNodeID(int nodeID) {
|
||||
return "" + (nodeID & 0xff) + '.' + ((nodeID >> 8) & 0xff);
|
||||
}
|
||||
|
||||
private static int[] parseToInt(String[] text) {
|
||||
try {
|
||||
int[] data = new int[text.length];
|
||||
for (int i = 0, n = data.length; i < n; i++) {
|
||||
data[i] = Integer.parseInt(text[i]);
|
||||
}
|
||||
return data;
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public double getCPUPower() {
|
||||
return (values[TIME_CPU] * POWER_CPU) / (values[TIME_CPU] + values[TIME_LPM]);
|
||||
}
|
||||
|
||||
public double getLPMPower() {
|
||||
return (values[TIME_LPM] * POWER_LPM) / (values[TIME_CPU] + values[TIME_LPM]);
|
||||
}
|
||||
|
||||
public double getListenPower() {
|
||||
return (values[TIME_LISTEN] * POWER_LISTEN) / (values[TIME_CPU] + values[TIME_LPM]);
|
||||
}
|
||||
|
||||
public double getTransmitPower() {
|
||||
return (values[TIME_TRANSMIT] * POWER_TRANSMIT) / (values[TIME_CPU] + values[TIME_LPM]);
|
||||
}
|
||||
|
||||
public double getAveragePower() {
|
||||
return (values[TIME_CPU] * POWER_CPU + values[TIME_LPM] * POWER_LPM
|
||||
+ values[TIME_LISTEN] * POWER_LISTEN + values[TIME_TRANSMIT] * POWER_TRANSMIT)
|
||||
/ (values[TIME_CPU] + values[TIME_LPM]);
|
||||
}
|
||||
|
||||
public long getPowerMeasureTime() {
|
||||
return (1000L * (values[TIME_CPU] + values[TIME_LPM])) / TICKS_PER_SECOND;
|
||||
}
|
||||
|
||||
public double getTemperature() {
|
||||
return -39.6 + 0.01 * values[TEMPERATURE];
|
||||
}
|
||||
|
||||
public double getBatteryVoltage() {
|
||||
return values[BATTERY_VOLTAGE] * 2 * 2.5 / 4096.0;
|
||||
}
|
||||
|
||||
public double getBatteryIndicator() {
|
||||
return values[BATTERY_INDICATOR];
|
||||
}
|
||||
|
||||
public double getRadioIntensity() {
|
||||
return values[RSSI];
|
||||
}
|
||||
|
||||
public double getLatency() {
|
||||
return values[LATENCY] / 8192.0;
|
||||
}
|
||||
|
||||
public double getHumidity() {
|
||||
double v = -4.0 + 405.0 * values[HUMIDITY] / 10000.0;
|
||||
if(v > 100) {
|
||||
return 100;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
public double getLight1() {
|
||||
return 10.0 * values[LIGHT1] / 7.0;
|
||||
}
|
||||
|
||||
public double getLight2() {
|
||||
return 46.0 * values[LIGHT2] / 10.0;
|
||||
}
|
||||
|
||||
public String getBestNeighborID() {
|
||||
return values[BEST_NEIGHBOR] > 0 ? mapNodeID(values[BEST_NEIGHBOR]): null;
|
||||
}
|
||||
|
||||
public double getBestNeighborETX() {
|
||||
return values[BEST_NEIGHBOR_ETX] / 8.0;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,321 @@
|
|||
/*
|
||||
* Copyright (c) 2008, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: SensorDataAggregator.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* SensorDataAggregator
|
||||
*
|
||||
* Authors : Joakim Eriksson, Niclas Finne
|
||||
* Created : 20 aug 2008
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package se.sics.contiki.collect;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class SensorDataAggregator implements SensorInfo {
|
||||
|
||||
private final Node node;
|
||||
private long[] values;
|
||||
private int minSeqno = Integer.MAX_VALUE;
|
||||
private int maxSeqno = Integer.MIN_VALUE;
|
||||
private int seqnoDelta = 0;
|
||||
private int dataCount;
|
||||
private int duplicates = 0;
|
||||
private int lost = 0;
|
||||
private int nodeRestartCount = 0;
|
||||
private int nextHopChangeCount = 0;
|
||||
private int lastNextHop = -1;
|
||||
private long shortestPeriod = Long.MAX_VALUE;
|
||||
private long longestPeriod = 0;
|
||||
|
||||
public SensorDataAggregator(Node node) {
|
||||
this.node = node;
|
||||
this.values = new long[VALUES_COUNT];
|
||||
}
|
||||
|
||||
public Node getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
public String getNodeID() {
|
||||
return node.getID();
|
||||
}
|
||||
|
||||
public long getValue(int index) {
|
||||
return values[index];
|
||||
}
|
||||
|
||||
public double getAverageValue(int index) {
|
||||
return dataCount > 0 ? (double)values[index] / (double)dataCount : 0;
|
||||
}
|
||||
|
||||
public int getValueCount() {
|
||||
return values.length;
|
||||
}
|
||||
|
||||
public int getDataCount() {
|
||||
return dataCount;
|
||||
}
|
||||
|
||||
public void addSensorData(SensorData data) {
|
||||
int seqn = data.getValue(SEQNO);
|
||||
int s = seqn + seqnoDelta;
|
||||
|
||||
int bestNeighbor = data.getValue(BEST_NEIGHBOR);
|
||||
if (lastNextHop != bestNeighbor && lastNextHop >= 0) {
|
||||
nextHopChangeCount++;
|
||||
}
|
||||
lastNextHop = bestNeighbor;
|
||||
|
||||
if (s <= maxSeqno) {
|
||||
// Check for duplicates among the last 5 packets
|
||||
for(int n = node.getSensorDataCount() - 1, i = n > 5 ? n - 5 : 0; i < n; i++) {
|
||||
SensorData sd = node.getSensorData(i);
|
||||
if (sd.getValue(SEQNO) != seqn || sd == data || sd.getValueCount() != data.getValueCount()) {
|
||||
// Not a duplicate
|
||||
} else if (Math.abs(data.getNodeTime() - sd.getNodeTime()) > 180000) {
|
||||
// Too long time between packets. Not a duplicate.
|
||||
// System.err.println("Too long time between packets with same seqno from "
|
||||
// + data.getNode() + ": "
|
||||
// + (Math.abs(data.getNodeTime() - sd.getNodeTime()) / 1000)
|
||||
// + " sec, " + (n - i) + " packets ago");
|
||||
} else {
|
||||
data.setDuplicate(true);
|
||||
|
||||
// Verify that the packet is a duplicate
|
||||
for(int j = DATA_LEN2, m = data.getValueCount(); j < m; j++) {
|
||||
if (sd.getValue(j) != data.getValue(j)) {
|
||||
data.setDuplicate(false);
|
||||
// System.out.println("NOT Duplicate: " + data.getNode() + " ("
|
||||
// + (n - i) + ": "
|
||||
// + ((data.getNodeTime() - sd.getNodeTime()) / 1000) + "sek): "
|
||||
// + seqn + " value[" + j + "]: " + sd.getValue(j) + " != "
|
||||
// + data.getValue(j));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (data.isDuplicate()) {
|
||||
// System.out.println("Duplicate: " + data.getNode() + ": " + seqn
|
||||
// + ": "
|
||||
// + (Math.abs(data.getNodeTime() - sd.getNodeTime()) / 1000)
|
||||
// + " sec, " + (n - i) + " packets ago");
|
||||
duplicates++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!data.isDuplicate()) {
|
||||
for (int i = 0, n = Math.min(VALUES_COUNT, data.getValueCount()); i < n; i++) {
|
||||
values[i] += data.getValue(i);
|
||||
}
|
||||
|
||||
if (node.getSensorDataCount() > 1) {
|
||||
long timeDiff = data.getNodeTime() - node.getSensorData(node.getSensorDataCount() - 2).getNodeTime();
|
||||
if (timeDiff > longestPeriod) {
|
||||
longestPeriod = timeDiff;
|
||||
}
|
||||
if (timeDiff < shortestPeriod) {
|
||||
shortestPeriod = timeDiff;
|
||||
}
|
||||
}
|
||||
if (dataCount == 0) {
|
||||
// First packet from node.
|
||||
} else if (maxSeqno - s > 2) {
|
||||
// Handle sequence number overflow.
|
||||
seqnoDelta = maxSeqno + 1;
|
||||
s = seqnoDelta + seqn;
|
||||
if (seqn > 127) {
|
||||
// Sequence number restarted at 128 (to separate node restarts
|
||||
// from sequence number overflow).
|
||||
seqn -= 128;
|
||||
seqnoDelta -= 128;
|
||||
s -= 128;
|
||||
} else {
|
||||
// Sequence number restarted at 0. This is usually an indication that
|
||||
// the node restarted.
|
||||
nodeRestartCount++;
|
||||
}
|
||||
if (seqn > 0) {
|
||||
lost += seqn;
|
||||
}
|
||||
} else if (s > maxSeqno + 1){
|
||||
lost += s - (maxSeqno + 1);
|
||||
}
|
||||
if (s < minSeqno) minSeqno = s;
|
||||
if (s > maxSeqno) maxSeqno = s;
|
||||
dataCount++;
|
||||
}
|
||||
data.setSeqno(s);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
for (int i = 0, n = values.length; i < n; i++) {
|
||||
values[i] = 0L;
|
||||
}
|
||||
dataCount = 0;
|
||||
duplicates = 0;
|
||||
lost = 0;
|
||||
nodeRestartCount = 0;
|
||||
nextHopChangeCount = 0;
|
||||
lastNextHop = 0;
|
||||
minSeqno = Integer.MAX_VALUE;
|
||||
maxSeqno = Integer.MIN_VALUE;
|
||||
seqnoDelta = 0;
|
||||
shortestPeriod = Long.MAX_VALUE;
|
||||
longestPeriod = 0;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0, n = values.length; i < n; i++) {
|
||||
if (i > 0) sb.append(' ');
|
||||
sb.append(values[i]);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public double getCPUPower() {
|
||||
return (values[TIME_CPU] * POWER_CPU) / (values[TIME_CPU] + values[TIME_LPM]);
|
||||
}
|
||||
|
||||
public double getLPMPower() {
|
||||
return (values[TIME_LPM] * POWER_LPM) / (values[TIME_CPU] + values[TIME_LPM]);
|
||||
}
|
||||
|
||||
public double getListenPower() {
|
||||
return (values[TIME_LISTEN] * POWER_LISTEN) / (values[TIME_CPU] + values[TIME_LPM]);
|
||||
}
|
||||
|
||||
public double getTransmitPower() {
|
||||
return (values[TIME_TRANSMIT] * POWER_TRANSMIT) / (values[TIME_CPU] + values[TIME_LPM]);
|
||||
}
|
||||
|
||||
public double getAveragePower() {
|
||||
return (values[TIME_CPU] * POWER_CPU + values[TIME_LPM] * POWER_LPM
|
||||
+ values[TIME_LISTEN] * POWER_LISTEN + values[TIME_TRANSMIT] * POWER_TRANSMIT)
|
||||
/ (values[TIME_CPU] + values[TIME_LPM]);
|
||||
}
|
||||
|
||||
public double getAverageDutyCycle(int index) {
|
||||
return (double)(values[index]) / (double)(values[TIME_CPU] + values[TIME_LPM]);
|
||||
}
|
||||
|
||||
public long getPowerMeasureTime() {
|
||||
return (1000L * (values[TIME_CPU] + values[TIME_LPM])) / TICKS_PER_SECOND;
|
||||
}
|
||||
|
||||
public double getAverageTemperature() {
|
||||
return dataCount > 0 ? (-39.6 + 0.01 * (values[TEMPERATURE] / dataCount)) : 0.0;
|
||||
}
|
||||
|
||||
public double getAverageRtmetric() {
|
||||
return getAverageValue(RTMETRIC);
|
||||
}
|
||||
|
||||
public double getAverageRadioIntensity() {
|
||||
return getAverageValue(RSSI);
|
||||
}
|
||||
|
||||
public double getAverageLatency() {
|
||||
return getAverageValue(LATENCY) / 4096.0;
|
||||
}
|
||||
|
||||
public double getAverageHumidity() {
|
||||
double v = 0.0;
|
||||
if (dataCount > 0) {
|
||||
v = -4.0 + 405.0 * (values[HUMIDITY] / dataCount) / 10000.0;
|
||||
}
|
||||
return v > 100 ? 100 : v;
|
||||
}
|
||||
|
||||
public double getAverageLight1() {
|
||||
return 10.0 * getAverageValue(LIGHT1) / 7.0;
|
||||
}
|
||||
|
||||
public double getAverageLight2() {
|
||||
return 46.0 * getAverageValue(LIGHT2) / 10.0;
|
||||
}
|
||||
|
||||
public double getAverageBestNeighborETX() {
|
||||
return getAverageValue(BEST_NEIGHBOR_ETX) / 8.0;
|
||||
}
|
||||
|
||||
public int getPacketCount() {
|
||||
return node.getSensorDataCount();
|
||||
}
|
||||
|
||||
public int getNextHopChangeCount() {
|
||||
return nextHopChangeCount;
|
||||
}
|
||||
|
||||
public int getEstimatedRestarts() {
|
||||
return nodeRestartCount;
|
||||
}
|
||||
|
||||
public int getEstimatedLostCount() {
|
||||
return lost;
|
||||
}
|
||||
|
||||
public int getDuplicateCount() {
|
||||
return duplicates;
|
||||
}
|
||||
|
||||
public int getMinSeqno() {
|
||||
return minSeqno;
|
||||
}
|
||||
|
||||
public int getMaxSeqno() {
|
||||
return maxSeqno;
|
||||
}
|
||||
|
||||
public long getAveragePeriod() {
|
||||
if (dataCount > 1) {
|
||||
long first = node.getSensorData(0).getNodeTime();
|
||||
long last = node.getSensorData(node.getSensorDataCount() - 1).getNodeTime();
|
||||
return (last - first) / dataCount;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getShortestPeriod() {
|
||||
return shortestPeriod < Long.MAX_VALUE ? shortestPeriod : 0;
|
||||
}
|
||||
|
||||
public long getLongestPeriod() {
|
||||
return longestPeriod;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright (c) 2008, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: SensorInfo.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* SensorInfo
|
||||
*
|
||||
* Authors : Joakim Eriksson, Niclas Finne
|
||||
* Created : 20 aug 2008
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package se.sics.contiki.collect;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public interface SensorInfo {
|
||||
|
||||
public static final long TICKS_PER_SECOND = 4096L;
|
||||
public static final double VOLTAGE = 3;
|
||||
public static final double POWER_CPU = 1.800 * VOLTAGE; /* mW */
|
||||
public static final double POWER_LPM = 0.0545 * VOLTAGE; /* mW */
|
||||
public static final double POWER_TRANSMIT = 17.7 * VOLTAGE; /* mW */
|
||||
public static final double POWER_LISTEN = 20.0 * VOLTAGE; /* mW */
|
||||
|
||||
public static final int DATA_LEN = 0;
|
||||
public static final int TIMESTAMP1 = 1;
|
||||
public static final int TIMESTAMP2 = 2;
|
||||
public static final int TIMESYNCTIMESTAMP = 3;
|
||||
public static final int NODE_ID = 4;
|
||||
public static final int SEQNO = 5;
|
||||
public static final int HOPS = 6;
|
||||
public static final int LATENCY = 7;
|
||||
public static final int DATA_LEN2 = 8;
|
||||
public static final int CLOCK = 9;
|
||||
public static final int TIMESYNCHTIME = 10;
|
||||
public static final int TIME_CPU = 11;
|
||||
public static final int TIME_LPM = 12;
|
||||
public static final int TIME_TRANSMIT = 13;
|
||||
public static final int TIME_LISTEN = 14;
|
||||
public static final int BEST_NEIGHBOR = 15;
|
||||
public static final int BEST_NEIGHBOR_ETX = 16;
|
||||
public static final int RTMETRIC = 17;
|
||||
public static final int NUM_NEIGHBORS = 18;
|
||||
public static final int BEACON_INTERVAL = 19;
|
||||
public static final int BATTERY_VOLTAGE = 20;
|
||||
public static final int BATTERY_INDICATOR = 21;
|
||||
public static final int LIGHT1 = 22;
|
||||
public static final int LIGHT2 = 23;
|
||||
public static final int TEMPERATURE = 24;
|
||||
public static final int HUMIDITY = 25;
|
||||
public static final int RSSI = 26;
|
||||
|
||||
public static final int VALUES_COUNT = 30;
|
||||
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Copyright (c) 2008, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: SerialConnection.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* SerialConnection
|
||||
*
|
||||
* Authors : Joakim Eriksson, Niclas Finne
|
||||
* Created : 5 jul 2008
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package se.sics.contiki.collect;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public abstract class SerialConnection {
|
||||
|
||||
protected final SerialConnectionListener listener;
|
||||
|
||||
protected boolean isSerialOutputSupported = true;
|
||||
|
||||
protected String comPort;
|
||||
protected boolean isOpen;
|
||||
protected boolean isClosed = true;
|
||||
protected String lastError;
|
||||
|
||||
protected PrintWriter serialOutput;
|
||||
|
||||
protected SerialConnection(SerialConnectionListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public boolean isMultiplePortsSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setSerialOutputSupported(boolean isSerialOutputSupported) {
|
||||
this.isSerialOutputSupported = isSerialOutputSupported;
|
||||
}
|
||||
|
||||
public boolean isSerialOutputSupported() {
|
||||
return isSerialOutputSupported;
|
||||
}
|
||||
|
||||
public boolean isOpen() {
|
||||
return isOpen;
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return isClosed;
|
||||
}
|
||||
|
||||
public abstract String getConnectionName();
|
||||
|
||||
public String getComPort() {
|
||||
return comPort;
|
||||
}
|
||||
|
||||
public void setComPort(String comPort) {
|
||||
this.comPort = comPort;
|
||||
}
|
||||
|
||||
public String getLastError() {
|
||||
return lastError;
|
||||
}
|
||||
|
||||
protected PrintWriter getSerialOutput() {
|
||||
return serialOutput;
|
||||
}
|
||||
|
||||
protected void setSerialOutput(PrintWriter serialOutput) {
|
||||
this.serialOutput = serialOutput;
|
||||
}
|
||||
|
||||
public void writeSerialData(String data) {
|
||||
PrintWriter serialOutput = this.serialOutput;
|
||||
if (serialOutput != null) {
|
||||
serialOutput.println(data);
|
||||
serialOutput.flush();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void open(String comPort);
|
||||
|
||||
public final void close() {
|
||||
isClosed = true;
|
||||
lastError = null;
|
||||
closeConnection();
|
||||
}
|
||||
|
||||
protected final void closeConnection() {
|
||||
isOpen = false;
|
||||
if (serialOutput != null) {
|
||||
serialOutput.close();
|
||||
serialOutput = null;
|
||||
}
|
||||
doClose();
|
||||
serialClosed();
|
||||
}
|
||||
|
||||
protected abstract void doClose();
|
||||
|
||||
protected final void serialData(String line) {
|
||||
listener.serialData(this, line);
|
||||
}
|
||||
|
||||
protected final void serialOpened() {
|
||||
listener.serialOpened(this);
|
||||
}
|
||||
|
||||
protected final void serialClosed() {
|
||||
listener.serialClosed(this);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2010, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: SerialConnectionListener.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* SerialConnectionListener
|
||||
*
|
||||
* Authors : Joakim Eriksson, Niclas Finne
|
||||
* Created : 5 oct 2010
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
package se.sics.contiki.collect;
|
||||
|
||||
public interface SerialConnectionListener {
|
||||
|
||||
public void serialData(SerialConnection connection, String line);
|
||||
|
||||
public void serialOpened(SerialConnection connection);
|
||||
|
||||
public void serialClosed(SerialConnection connection);
|
||||
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright (c) 2010, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: SerialDumpConnection.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* SerialDumpConnection
|
||||
*
|
||||
* Authors : Joakim Eriksson, Niclas Finne
|
||||
* Created : 5 oct 2010
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package se.sics.contiki.collect;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class SerialDumpConnection extends CommandConnection {
|
||||
|
||||
public static final String SERIALDUMP_WINDOWS = "./tools/serialdump-windows.exe";
|
||||
public static final String SERIALDUMP_LINUX = "./tools/serialdump-linux";
|
||||
|
||||
public SerialDumpConnection(SerialConnectionListener listener) {
|
||||
super(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMultiplePortsSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConnectionName() {
|
||||
return comPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open(String comPort) {
|
||||
if (comPort == null) {
|
||||
throw new IllegalStateException("no com port");
|
||||
}
|
||||
|
||||
/* Connect to COM using external serialdump application */
|
||||
String osName = System.getProperty("os.name").toLowerCase();
|
||||
String fullCommand;
|
||||
if (osName.startsWith("win")) {
|
||||
fullCommand = SERIALDUMP_WINDOWS + " " + "-b115200" + " " + getMappedComPortForWindows(comPort);
|
||||
} else {
|
||||
fullCommand = SERIALDUMP_LINUX + " " + "-b115200" + " " + comPort;
|
||||
}
|
||||
setCommand(fullCommand);
|
||||
super.open(comPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void standardData(String line) {
|
||||
serialData(line);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void errorData(String line) {
|
||||
if (!isOpen && line.startsWith("connecting") && line.endsWith("[OK]")) {
|
||||
isOpen = true;
|
||||
serialOpened();
|
||||
} else {
|
||||
super.errorData(line);
|
||||
}
|
||||
}
|
||||
|
||||
private String getMappedComPortForWindows(String comPort) {
|
||||
if (comPort.startsWith("COM")) {
|
||||
comPort = "/dev/com" + comPort.substring(3);
|
||||
}
|
||||
return comPort;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright (c) 2010, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: StdinConnection.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* StdinConnection
|
||||
*
|
||||
* Authors : Niclas Finne
|
||||
* Created : 5 oct 2010
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package se.sics.contiki.collect;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class StdinConnection extends SerialConnection {
|
||||
|
||||
private PrintWriter stdout;
|
||||
|
||||
public StdinConnection(SerialConnectionListener listener) {
|
||||
super(listener);
|
||||
// Redirect standard out as standard err to use standard out for serial output
|
||||
stdout = new PrintWriter(new OutputStreamWriter(System.out));
|
||||
System.setOut(System.err);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConnectionName() {
|
||||
return "<stdin>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open(String comPort) {
|
||||
close();
|
||||
this.comPort = comPort == null ? "" : comPort;
|
||||
|
||||
isClosed = false;
|
||||
try {
|
||||
final BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
|
||||
setSerialOutput(stdout);
|
||||
|
||||
/* Start thread listening on standard in */
|
||||
Thread readInput = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
String line;
|
||||
try {
|
||||
while ((line = input.readLine()) != null) {
|
||||
serialData(line);
|
||||
// Do not send data too fast
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
input.close();
|
||||
System.out.println("SerialConnection stdin terminated.");
|
||||
closeConnection();
|
||||
} catch (IOException e) {
|
||||
lastError = "Error when reading from SerialConnection stdin: " + e;
|
||||
System.err.println(lastError);
|
||||
if (!isClosed) {
|
||||
e.printStackTrace();
|
||||
closeConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, "read input stream thread");
|
||||
|
||||
isOpen = true;
|
||||
serialOpened();
|
||||
readInput.start();
|
||||
|
||||
} catch (Exception e) {
|
||||
lastError = "Failed to open stdin for reading: " + e;
|
||||
System.err.println(lastError);
|
||||
e.printStackTrace();
|
||||
closeConnection();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doClose() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2008, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: Visualizer.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* Visualizer
|
||||
*
|
||||
* Authors : Joakim Eriksson, Niclas Finne
|
||||
* Created : 3 jul 2008
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package se.sics.contiki.collect;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public interface Visualizer {
|
||||
|
||||
public String getCategory();
|
||||
public String getTitle();
|
||||
public Component getPanel();
|
||||
public void nodesSelected(Node[] node);
|
||||
public void nodeAdded(Node node);
|
||||
public void nodeDataReceived(SensorData sensorData);
|
||||
public void clearNodeData();
|
||||
|
||||
}
|
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* Copyright (c) 2010, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: AggregatedTimeChartPanel.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* PacketChartPanel
|
||||
*
|
||||
* Authors : Joakim Eriksson, Niclas Finne
|
||||
* Created : 6 sep 2010
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package se.sics.contiki.collect.gui;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import org.jfree.chart.ChartFactory;
|
||||
import org.jfree.chart.ChartPanel;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
|
||||
import org.jfree.data.time.Minute;
|
||||
import org.jfree.data.time.TimeSeries;
|
||||
import org.jfree.data.time.TimeSeriesCollection;
|
||||
|
||||
import se.sics.contiki.collect.CollectServer;
|
||||
import se.sics.contiki.collect.Node;
|
||||
import se.sics.contiki.collect.SensorData;
|
||||
import se.sics.contiki.collect.Visualizer;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public abstract class AggregatedTimeChartPanel<T> extends JPanel implements Visualizer {
|
||||
|
||||
private static final long serialVersionUID = 2100788758213434540L;
|
||||
|
||||
protected final CollectServer server;
|
||||
protected final String category;
|
||||
protected final String title;
|
||||
protected final TimeSeries series;
|
||||
|
||||
protected final JFreeChart chart;
|
||||
protected final ChartPanel chartPanel;
|
||||
|
||||
private Node[] selectedNodes;
|
||||
private HashMap<Node,T> selectedMap = new HashMap<Node,T>();
|
||||
|
||||
public AggregatedTimeChartPanel(CollectServer server, String category, String title,
|
||||
String timeAxisLabel, String valueAxisLabel) {
|
||||
super(new BorderLayout());
|
||||
this.server = server;
|
||||
this.category = category;
|
||||
this.title = title;
|
||||
this.series = new TimeSeries(title, Minute.class);
|
||||
TimeSeriesCollection timeSeries = new TimeSeriesCollection(series);
|
||||
this.chart = ChartFactory.createTimeSeriesChart(
|
||||
title, timeAxisLabel, valueAxisLabel, timeSeries,
|
||||
false, true, false
|
||||
);
|
||||
this.chartPanel = new ChartPanel(chart);
|
||||
this.chartPanel.setPreferredSize(new Dimension(500, 270));
|
||||
setBaseShapeVisible(false);
|
||||
add(chartPanel, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getPanel() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodeAdded(Node node) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodesSelected(Node[] nodes) {
|
||||
if (isVisible()) {
|
||||
updateSelected(nodes);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSelected(Node[] nodes) {
|
||||
if (this.selectedNodes != nodes) {
|
||||
this.selectedNodes = nodes;
|
||||
this.selectedMap.clear();
|
||||
if (nodes != null) {
|
||||
for(Node node : nodes) {
|
||||
this.selectedMap.put(node, createState(node));
|
||||
}
|
||||
}
|
||||
updateCharts();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodeDataReceived(SensorData data) {
|
||||
if (isVisible() && selectedMap.get(data.getNode()) != null) {
|
||||
updateCharts();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearNodeData() {
|
||||
if (isVisible()) {
|
||||
updateCharts();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCharts() {
|
||||
int duplicates = 0;
|
||||
int total = 0;
|
||||
series.clear();
|
||||
if (this.selectedNodes != null && server.getSensorDataCount() > 0) {
|
||||
long minute = server.getSensorData(0).getNodeTime() / 60000;
|
||||
long lastMinute = minute;
|
||||
int count = 0;
|
||||
clearState(selectedMap);
|
||||
for(int i = 0; i < server.getSensorDataCount(); i++) {
|
||||
SensorData sd = server.getSensorData(i);
|
||||
T nodeState = selectedMap.get(sd.getNode());
|
||||
if (nodeState != null) {
|
||||
if (sd.isDuplicate()) {
|
||||
duplicates++;
|
||||
} else {
|
||||
long min = sd.getNodeTime() / 60000;
|
||||
if (min != minute) {
|
||||
if (lastMinute < minute) {
|
||||
series.add(new Minute(new Date(lastMinute * 60000L)), 0);
|
||||
if (lastMinute < minute - 1) {
|
||||
series.add(new Minute(new Date((minute - 1) * 60000L)), 0);
|
||||
}
|
||||
}
|
||||
series.add(new Minute(new Date(minute * 60000L)), count);
|
||||
count = 0;
|
||||
lastMinute = minute + 1;
|
||||
minute = min;
|
||||
}
|
||||
count += getSensorDataValue(sd, nodeState);
|
||||
}
|
||||
total++;
|
||||
}
|
||||
}
|
||||
}
|
||||
chart.setTitle(getTitle(selectedMap.size(), total, duplicates));
|
||||
}
|
||||
|
||||
protected String getTitle(int nodeCount, int dataCount, int duplicateCount) {
|
||||
return title;
|
||||
}
|
||||
|
||||
protected abstract T createState(Node node);
|
||||
|
||||
protected void clearState(Map<Node,T> map) {
|
||||
}
|
||||
|
||||
protected abstract int getSensorDataValue(SensorData sd, T nodeState);
|
||||
|
||||
public boolean getBaseShapeVisible() {
|
||||
return ((XYLineAndShapeRenderer)this.chart.getXYPlot().getRenderer()).getBaseShapesVisible();
|
||||
}
|
||||
|
||||
public void setBaseShapeVisible(boolean visible) {
|
||||
((XYLineAndShapeRenderer)this.chart.getXYPlot().getRenderer()).setBaseShapesVisible(visible);
|
||||
}
|
||||
|
||||
public double getRangeMinimumSize() {
|
||||
return chart.getXYPlot().getRangeAxis().getAutoRangeMinimumSize();
|
||||
}
|
||||
|
||||
public void setRangeMinimumSize(double size) {
|
||||
chart.getXYPlot().getRangeAxis().setAutoRangeMinimumSize(size);
|
||||
}
|
||||
|
||||
public void setVisible(boolean visible) {
|
||||
updateSelected(visible ? server.getSelectedNodes() : null);
|
||||
super.setVisible(visible);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
* Copyright (c) 2008, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: BarChartPanel.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* PowerPanel
|
||||
*
|
||||
* Authors : Joakim Eriksson, Niclas Finne
|
||||
* Created : 5 jul 2008
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package se.sics.contiki.collect.gui;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.GradientPaint;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import javax.swing.JPanel;
|
||||
import org.jfree.chart.ChartFactory;
|
||||
import org.jfree.chart.ChartPanel;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.jfree.chart.plot.CategoryPlot;
|
||||
import org.jfree.chart.plot.PlotOrientation;
|
||||
import org.jfree.chart.renderer.category.BarRenderer;
|
||||
import org.jfree.data.category.DefaultCategoryDataset;
|
||||
import se.sics.contiki.collect.CollectServer;
|
||||
import se.sics.contiki.collect.Node;
|
||||
import se.sics.contiki.collect.SensorData;
|
||||
import se.sics.contiki.collect.Visualizer;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public abstract class BarChartPanel extends JPanel implements Visualizer {
|
||||
|
||||
private static final long serialVersionUID = 7664283678708048061L;
|
||||
|
||||
protected final CollectServer server;
|
||||
protected final String category;
|
||||
protected final String title;
|
||||
protected final String[] categories;
|
||||
protected final JFreeChart chart;
|
||||
protected final ChartPanel chartPanel;
|
||||
protected final DefaultCategoryDataset dataset;
|
||||
|
||||
private boolean isShowingAllNodes = false;
|
||||
private int categoryOrder = 0;
|
||||
|
||||
protected BarChartPanel(CollectServer server, String category, String title,
|
||||
String chartTitle, String domainAxisLabel, String valueAxisLabel,
|
||||
String[] categories) {
|
||||
this(server, category, title, chartTitle, domainAxisLabel, valueAxisLabel, categories, true);
|
||||
}
|
||||
|
||||
protected BarChartPanel(CollectServer server, String category, String title,
|
||||
String chartTitle, String domainAxisLabel, String valueAxisLabel,
|
||||
String[] categories, boolean stackedChart) {
|
||||
super(new BorderLayout());
|
||||
this.server = server;
|
||||
this.category = category;
|
||||
this.title = title;
|
||||
this.categories = categories;
|
||||
|
||||
/* Create chart with power of all nodes */
|
||||
dataset = new DefaultCategoryDataset();
|
||||
if (stackedChart) {
|
||||
this.chart = ChartFactory.createStackedBarChart(chartTitle,
|
||||
domainAxisLabel, valueAxisLabel, dataset, PlotOrientation.VERTICAL,
|
||||
categories.length > 1, true, false);
|
||||
} else {
|
||||
this.chart = ChartFactory.createBarChart(chartTitle,
|
||||
domainAxisLabel, valueAxisLabel, dataset, PlotOrientation.VERTICAL,
|
||||
categories.length > 1, true, false);
|
||||
if (categories.length > 1) {
|
||||
CategoryPlot plot = chart.getCategoryPlot();
|
||||
BarRenderer renderer = (BarRenderer) plot.getRenderer();
|
||||
renderer.setItemMargin(0);
|
||||
}
|
||||
}
|
||||
this.chartPanel = new ChartPanel(chart, false);
|
||||
this.chartPanel.setPreferredSize(new Dimension(500, 270));
|
||||
if (categories.length > 1) {
|
||||
this.chartPanel.addMouseListener(new MouseAdapter() {
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
categoryOrder++;
|
||||
updateCharts();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
CategoryPlot plot = (CategoryPlot) chart.getPlot();
|
||||
BarRenderer renderer = (BarRenderer) plot.getRenderer();
|
||||
if (categories.length < 3) {
|
||||
renderer.setDrawBarOutline(false);
|
||||
|
||||
GradientPaint gp = new GradientPaint(0.0f, 0.0f, Color.RED,
|
||||
0.0f, 0.0f, new Color(128, 0, 0));
|
||||
renderer.setSeriesPaint(0, gp);
|
||||
if (categories.length > 1) {
|
||||
gp = new GradientPaint(0.0f, 0.0f, Color.BLUE,
|
||||
0.0f, 0.0f, new Color(0, 0, 128));
|
||||
renderer.setSeriesPaint(1, gp);
|
||||
}
|
||||
} else {
|
||||
renderer.setDrawBarOutline(true);
|
||||
}
|
||||
|
||||
add(chartPanel, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getPanel() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isShowingAllNodes() {
|
||||
return isShowingAllNodes;
|
||||
}
|
||||
|
||||
public void setShowingAllNodes(boolean isShowingAllNodes) {
|
||||
if (this.isShowingAllNodes != isShowingAllNodes) {
|
||||
this.isShowingAllNodes = isShowingAllNodes;
|
||||
if (isVisible()) {
|
||||
updateCharts();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodeAdded(Node node) {
|
||||
if (isVisible()) {
|
||||
int count = node.getSensorDataCount();
|
||||
if (count > 0 || isShowingAllNodes) {
|
||||
addNode(node);
|
||||
}
|
||||
if (count > 0) {
|
||||
addSensorData(node.getSensorData(count - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodesSelected(Node[] nodes) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodeDataReceived(SensorData data) {
|
||||
if (isVisible()) {
|
||||
addSensorData(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearNodeData() {
|
||||
if (isVisible()) {
|
||||
updateCharts();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCharts() {
|
||||
dataset.clear();
|
||||
Node[] nodes = server.getNodes();
|
||||
if (nodes != null) {
|
||||
for (int i = 0, n = nodes.length; i < n; i++) {
|
||||
int count = nodes[i].getSensorDataCount();
|
||||
if (count > 0 || isShowingAllNodes) {
|
||||
addNode(nodes[i]);
|
||||
}
|
||||
if (count > 0) {
|
||||
addSensorData(nodes[i].getSensorData(count - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addNode(Node node) {
|
||||
String name = node.getName();
|
||||
for (int j = 0, m = categories.length; j < m; j++) {
|
||||
dataset.addValue(0, categories[(j + categoryOrder) % categories.length], name);
|
||||
}
|
||||
}
|
||||
|
||||
public void setVisible(boolean visible) {
|
||||
if (visible) {
|
||||
updateCharts();
|
||||
} else {
|
||||
dataset.clear();
|
||||
}
|
||||
super.setVisible(visible);
|
||||
}
|
||||
|
||||
protected abstract void addSensorData(SensorData data);
|
||||
|
||||
}
|
846
tools/collect-view/src/se/sics/contiki/collect/gui/MapPanel.java
Normal file
846
tools/collect-view/src/se/sics/contiki/collect/gui/MapPanel.java
Normal file
|
@ -0,0 +1,846 @@
|
|||
/*
|
||||
* Copyright (c) 2008, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: MapPanel.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* MapPanel
|
||||
*
|
||||
* Authors : Joakim Eriksson, Niclas Finne
|
||||
* Created : 3 jul 2008
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package se.sics.contiki.collect.gui;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.GridLayout;
|
||||
import java.awt.Polygon;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.event.MouseMotionListener;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Properties;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JCheckBoxMenuItem;
|
||||
import javax.swing.JFormattedTextField;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JSlider;
|
||||
import javax.swing.Timer;
|
||||
import javax.swing.border.LineBorder;
|
||||
import javax.swing.border.TitledBorder;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.swing.plaf.basic.BasicGraphicsUtils;
|
||||
|
||||
import se.sics.contiki.collect.CollectServer;
|
||||
import se.sics.contiki.collect.Configurable;
|
||||
import se.sics.contiki.collect.Link;
|
||||
import se.sics.contiki.collect.Node;
|
||||
import se.sics.contiki.collect.SensorData;
|
||||
import se.sics.contiki.collect.Visualizer;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class MapPanel extends JPanel implements Configurable, Visualizer, ActionListener, MouseListener, MouseMotionListener {
|
||||
|
||||
private static final long serialVersionUID = -8256619482599309425L;
|
||||
|
||||
private static final Logger log =
|
||||
Logger.getLogger(MapPanel.class.getName());
|
||||
|
||||
private static final boolean VISUAL_DRAG = true;
|
||||
|
||||
private static final Color LINK_COLOR = new Color(0x40, 0x40, 0xf0, 0xff);
|
||||
|
||||
private static final int delta = 7;
|
||||
|
||||
private final CollectServer server;
|
||||
private final String category;
|
||||
private final boolean isMap;
|
||||
private String title;
|
||||
|
||||
private Timer timer;
|
||||
|
||||
private JPopupMenu popupMenu;
|
||||
private JCheckBoxMenuItem layoutItem;
|
||||
private JCheckBoxMenuItem lockedItem;
|
||||
private JMenuItem shakeItem;
|
||||
// private JCheckBoxMenuItem dragItem;
|
||||
private JCheckBoxMenuItem backgroundItem;
|
||||
private JCheckBoxMenuItem showNetworkItem;
|
||||
private JCheckBoxMenuItem configItem;
|
||||
private JMenuItem resetNetworkItem;
|
||||
private MapNode popupNode;
|
||||
|
||||
private Hashtable<String,MapNode> nodeTable = new Hashtable<String,MapNode>();
|
||||
private MapNode[] nodeList = new MapNode[0];
|
||||
private boolean updateNodeList;
|
||||
|
||||
private MapNode selectedNode;
|
||||
private ArrayList<MapNode> selectedMapNodes = new ArrayList<MapNode>();
|
||||
private Node[] selectedNodes;
|
||||
private MapNode draggedNode;
|
||||
private long draggedTime;
|
||||
|
||||
private ImageIcon mapImage;
|
||||
private String mapName;
|
||||
private boolean showBackground;
|
||||
|
||||
private int layoutRepel = 100;
|
||||
private int layoutAttract = 50;
|
||||
private int layoutGravity = 1;
|
||||
|
||||
private double etxFactor = 1.0;
|
||||
|
||||
private boolean isLayoutActive = true;
|
||||
private boolean hideNetwork = false;
|
||||
|
||||
protected JPanel configPanel;
|
||||
|
||||
public MapPanel(CollectServer server, String title, String category, boolean isMap) {
|
||||
super(null);
|
||||
this.server = server;
|
||||
this.title = title;
|
||||
this.category = category;
|
||||
this.isMap = isMap;
|
||||
setPreferredSize(new Dimension(300, 200));
|
||||
|
||||
popupMenu = new JPopupMenu(getTitle());
|
||||
if (!isMap) {
|
||||
layoutItem = createCheckBoxMenuItem(popupMenu, "Update Layout", isLayoutActive);
|
||||
popupMenu.add(layoutItem);
|
||||
|
||||
lockedItem = createCheckBoxMenuItem(popupMenu, "Fixed Node Position", false);
|
||||
shakeItem = createMenuItem(popupMenu, "Shake Nodes");
|
||||
popupMenu.addSeparator();
|
||||
}
|
||||
|
||||
showNetworkItem = createCheckBoxMenuItem(popupMenu, "Show Network Info", true);
|
||||
resetNetworkItem = createMenuItem(popupMenu, "Reset Network");
|
||||
popupMenu.addSeparator();
|
||||
if (isMap) {
|
||||
backgroundItem = createCheckBoxMenuItem(popupMenu, "Show Background", false);
|
||||
backgroundItem.setEnabled(false);
|
||||
} else {
|
||||
configItem = createCheckBoxMenuItem(popupMenu, "Show Layout Settings", false);
|
||||
}
|
||||
|
||||
// popupMenu.addSeparator();
|
||||
// dragItem = new JCheckBoxMenuItem("Visible Drag", true);
|
||||
// popupMenu.add(dragItem);
|
||||
|
||||
setBackground(Color.white);
|
||||
addMouseListener(this);
|
||||
addMouseMotionListener(this);
|
||||
|
||||
if (!isMap) {
|
||||
timer = new Timer(100, this);
|
||||
|
||||
configPanel = new JPanel(new GridLayout(0, 1));
|
||||
configPanel.setBorder(LineBorder.createBlackLineBorder());
|
||||
|
||||
JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 1000, 1000 - layoutAttract);
|
||||
slider.setBorder(new TitledBorder("Attract Factor: " + (1000 - layoutAttract)));
|
||||
slider.addChangeListener(new ChangeListener() {
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
JSlider slider = (JSlider)e.getSource();
|
||||
layoutAttract = 1000 - slider.getValue();
|
||||
((TitledBorder)slider.getBorder()).setTitle("Attract Factor: " + slider.getValue());
|
||||
}
|
||||
});
|
||||
configPanel.add(slider);
|
||||
|
||||
slider = new JSlider(JSlider.HORIZONTAL, 0, 1000, layoutRepel);
|
||||
slider.setBorder(new TitledBorder("Repel Range: " + layoutRepel));
|
||||
slider.addChangeListener(new ChangeListener() {
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
JSlider slider = (JSlider)e.getSource();
|
||||
layoutRepel = slider.getValue();
|
||||
((TitledBorder)slider.getBorder()).setTitle("Repel Range: " + layoutRepel);
|
||||
}
|
||||
});
|
||||
configPanel.add(slider);
|
||||
|
||||
slider = new JSlider(JSlider.HORIZONTAL, 0, 100, layoutGravity);
|
||||
slider.setBorder(new TitledBorder("Gravity: " + layoutGravity));
|
||||
slider.addChangeListener(new ChangeListener() {
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
JSlider slider = (JSlider)e.getSource();
|
||||
layoutGravity = slider.getValue();
|
||||
((TitledBorder)slider.getBorder()).setTitle("Gravity: " + layoutGravity);
|
||||
}
|
||||
});
|
||||
configPanel.add(slider);
|
||||
|
||||
final JFormattedTextField etxField = new JFormattedTextField(new Double(etxFactor));
|
||||
etxField.setBorder(BorderFactory.createTitledBorder("ETX factor"));
|
||||
etxField.setColumns(5);
|
||||
etxField.addPropertyChangeListener("value", new PropertyChangeListener() {
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
etxFactor = ((Number)etxField.getValue()).doubleValue();
|
||||
repaint();
|
||||
}
|
||||
});
|
||||
configPanel.add(etxField);
|
||||
|
||||
configPanel.setVisible(false);
|
||||
add(configPanel);
|
||||
|
||||
addComponentListener(new ComponentAdapter() {
|
||||
public void componentResized(ComponentEvent ev) {
|
||||
if (configPanel.isVisible()) {
|
||||
updateConfigLayout();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public String getMapBackground() {
|
||||
return isMap ? mapName : null;
|
||||
}
|
||||
|
||||
public boolean setMapBackground(String image) {
|
||||
if (!isMap) {
|
||||
return false;
|
||||
}
|
||||
if (image == null) {
|
||||
mapImage = null;
|
||||
mapName = null;
|
||||
backgroundItem.setEnabled(false);
|
||||
backgroundItem.setSelected(false);
|
||||
showBackground = false;
|
||||
repaint();
|
||||
return true;
|
||||
}
|
||||
|
||||
ImageIcon ii = new ImageIcon(image);
|
||||
if (ii.getIconWidth() <= 0 || ii.getIconHeight() <= 0) {
|
||||
log.warning("could not find image '" + image + '\'');
|
||||
return false;
|
||||
}
|
||||
mapImage = ii;
|
||||
mapName = image;
|
||||
setPreferredSize(new Dimension(ii.getIconWidth(), ii.getIconHeight()));
|
||||
showBackground = true;
|
||||
backgroundItem.setEnabled(true);
|
||||
backgroundItem.setSelected(true);
|
||||
repaint();
|
||||
return true;
|
||||
}
|
||||
|
||||
private JCheckBoxMenuItem createCheckBoxMenuItem(JPopupMenu menu, String title, boolean isSelected) {
|
||||
JCheckBoxMenuItem item = new JCheckBoxMenuItem(title, isSelected);
|
||||
item.addActionListener(this);
|
||||
menu.add(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
private JMenuItem createMenuItem(JPopupMenu menu, String title) {
|
||||
JMenuItem item = new JMenuItem(title);
|
||||
item.addActionListener(this);
|
||||
menu.add(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
public void setVisible(boolean visible) {
|
||||
if (visible) {
|
||||
clear();
|
||||
if (timer != null) {
|
||||
timer.start();
|
||||
}
|
||||
} else {
|
||||
if (timer != null) {
|
||||
timer.stop();
|
||||
}
|
||||
}
|
||||
super.setVisible(visible);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
setCursor(Cursor.getDefaultCursor());
|
||||
draggedNode = null;
|
||||
updateSelected();
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Node handling
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
public Node getNode(String id) {
|
||||
MapNode node = nodeTable.get(id);
|
||||
return node != null ? node.node : null;
|
||||
}
|
||||
|
||||
public MapNode getMapNode(String id) {
|
||||
return nodeTable.get(id);
|
||||
}
|
||||
|
||||
private MapNode addMapNode(Node nd) {
|
||||
String id = nd.getID();
|
||||
MapNode node = nodeTable.get(id);
|
||||
if (node == null) {
|
||||
node = new MapNode(this, nd);
|
||||
node.y = 10 + (int) (Math.random() * (Math.max(100, getHeight()) - 20));
|
||||
node.x = 10 + (int) (Math.random() * (Math.max(100, getWidth()) - 30));
|
||||
|
||||
String location = server.getConfig(isMap ? id : ("collect.map." + id));
|
||||
if (location != null) {
|
||||
try {
|
||||
String[] pos = location.split(",");
|
||||
node.x = Integer.parseInt(pos[0].trim());
|
||||
node.y = Integer.parseInt(pos[1].trim());
|
||||
node.hasFixedLocation = !isMap;
|
||||
} catch (Exception e) {
|
||||
System.err.println("could not parse node location: " + location);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
nodeTable.put(id, node);
|
||||
updateNodeList = true;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
private MapNode[] getNodeList() {
|
||||
if (updateNodeList) {
|
||||
synchronized (nodeTable) {
|
||||
updateNodeList = false;
|
||||
nodeList = nodeTable.values().toArray(new MapNode[nodeTable.size()]);
|
||||
}
|
||||
}
|
||||
return nodeList;
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Visualizer
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getPanel() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodesSelected(Node[] nodes) {
|
||||
if (selectedNodes != nodes) {
|
||||
selectedNodes = nodes;
|
||||
if (isVisible()) {
|
||||
updateSelected();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSelected() {
|
||||
if (selectedMapNodes.size() > 0) {
|
||||
for(MapNode node : selectedMapNodes) {
|
||||
node.isSelected = false;
|
||||
}
|
||||
selectedMapNodes.clear();
|
||||
}
|
||||
|
||||
if (selectedNodes == null || selectedNodes.length == 0) {
|
||||
selectedNode = null;
|
||||
} else {
|
||||
for (Node node : selectedNodes) {
|
||||
MapNode mapNode = addMapNode(node);
|
||||
selectedMapNodes.add(mapNode);
|
||||
mapNode.isSelected = true;
|
||||
}
|
||||
selectedNode = selectedMapNodes.get(0);
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodeAdded(Node nd) {
|
||||
addMapNode(nd);
|
||||
if (isVisible()) {
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodeDataReceived(SensorData sensorData) {
|
||||
if (isVisible()) {
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearNodeData() {
|
||||
nodeTable.clear();
|
||||
updateNodeList = true;
|
||||
nodesSelected(null);
|
||||
if (isVisible()) {
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Graphics
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
Graphics2D g2d = (Graphics2D) g;
|
||||
FontMetrics fm = g.getFontMetrics();
|
||||
int fnHeight = fm.getHeight();
|
||||
int fnDescent = fm.getDescent();
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
|
||||
g.setColor(getBackground());
|
||||
g.fillRect(0, 0, width, height);
|
||||
if (showBackground && isMap) {
|
||||
mapImage.paintIcon(this, g, 0, 0);
|
||||
}
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
// Display legend
|
||||
if (!hideNetwork) {
|
||||
int legendWidth = fm.stringWidth("ETX");
|
||||
g.setColor(Color.black);
|
||||
g.drawString("ETX", width - legendWidth - 10, 10 + fnHeight - fnDescent);
|
||||
g.drawRect(width - legendWidth - 30, 8, legendWidth + 24, fnHeight + 4);
|
||||
g.setColor(LINK_COLOR);
|
||||
g2d.drawLine(width - legendWidth - 25, 10 + fnHeight / 2,
|
||||
width - legendWidth - 15, 10 + fnHeight / 2);
|
||||
}
|
||||
|
||||
MapNode[] nodes = getNodeList();
|
||||
if (!isMap || !hideNetwork) {
|
||||
g.setColor(LINK_COLOR);
|
||||
for (MapNode n : nodes) {
|
||||
for (int j = 0, mu = n.node.getLinkCount(); j < mu; j++) {
|
||||
Link link = n.node.getLink(j);
|
||||
MapNode linkNode = addMapNode(link.node);
|
||||
int x2 = linkNode.x;
|
||||
int y2 = linkNode.y;
|
||||
g2d.drawLine(n.x, n.y, x2, y2);
|
||||
drawArrow(g, n.x, n.y, x2, y2, 3);
|
||||
if (!hideNetwork) {
|
||||
int xn1, xn2, yn1, yn2;
|
||||
if (n.x <= x2) {
|
||||
xn1 = n.x; xn2 = x2;
|
||||
yn1 = n.y; yn2 = y2;
|
||||
} else {
|
||||
xn1 = x2; xn2 = n.x;
|
||||
yn1 = y2; yn2 = n.y;
|
||||
}
|
||||
int dx = xn1 + (xn2 - xn1) / 2 + 4;
|
||||
int dy = yn1 + (yn2 - yn1) / 2 - fnDescent;
|
||||
if (yn2 < yn1) {
|
||||
dy += fnHeight - fnDescent;
|
||||
}
|
||||
g.drawString(
|
||||
Double.toString(((int) (link.getETX() * etxFactor * 100 + 0.5)) / 100.0),
|
||||
dx, dy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (MapNode n : nodes) {
|
||||
n.paint(g, n.x, n.y);
|
||||
|
||||
g.setColor(Color.black);
|
||||
if (n.isSelected) {
|
||||
BasicGraphicsUtils.drawDashedRect(g, n.x - delta, n.y - delta,
|
||||
2 * delta, 2 * delta);
|
||||
}
|
||||
if (selectedNode != null && selectedNode.message != null) {
|
||||
g.drawString(selectedNode.message, 10, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Polygon arrowPoly = new Polygon();
|
||||
private void drawArrow(Graphics g, int xSource, int ySource, int xDest, int yDest, int delta) {
|
||||
double dx = xSource - xDest;
|
||||
double dy = ySource - yDest;
|
||||
double dir = Math.atan2(dx, dy);
|
||||
double len = Math.sqrt(dx * dx + dy * dy);
|
||||
dx /= len;
|
||||
dy /= len;
|
||||
len -= delta;
|
||||
xDest = xSource - (int) (dx * len);
|
||||
yDest = ySource - (int) (dy * len);
|
||||
g.drawLine(xDest, yDest, xSource, ySource);
|
||||
|
||||
final int size = 8;
|
||||
arrowPoly.reset();
|
||||
arrowPoly.addPoint(xDest, yDest);
|
||||
arrowPoly.addPoint(xDest + xCor(size, dir + 0.5), yDest + yCor(size, dir + 0.5));
|
||||
arrowPoly.addPoint(xDest + xCor(size, dir - 0.5), yDest + yCor(size, dir - 0.5));
|
||||
arrowPoly.addPoint(xDest, yDest);
|
||||
g.fillPolygon(arrowPoly);
|
||||
}
|
||||
|
||||
private int yCor(int len, double dir) {
|
||||
return (int)(0.5 + len * Math.cos(dir));
|
||||
}
|
||||
|
||||
private int xCor(int len, double dir) {
|
||||
return (int)(0.5 + len * Math.sin(dir));
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// ActionListener
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
Object source = e.getSource();
|
||||
if (!isMap && source == timer) {
|
||||
if (isLayoutActive) {
|
||||
updateNodeLayout();
|
||||
repaint();
|
||||
}
|
||||
|
||||
} else if (!isMap && source == lockedItem) {
|
||||
if (popupNode != null) {
|
||||
popupNode.hasFixedLocation = lockedItem.isSelected();
|
||||
repaint();
|
||||
}
|
||||
|
||||
} else if (!isMap && source == layoutItem) {
|
||||
isLayoutActive = layoutItem.isSelected();
|
||||
|
||||
} else if (!isMap && source == shakeItem) {
|
||||
for(MapNode n : getNodeList()) {
|
||||
if (!n.hasFixedLocation) {
|
||||
n.x += Math.random() * 100 - 50;
|
||||
n.y += Math.random() * 100 - 50;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (!isMap && source == configItem) {
|
||||
if (configItem.isSelected()) {
|
||||
configPanel.setSize(getPreferredSize());
|
||||
configPanel.validate();
|
||||
updateConfigLayout();
|
||||
configPanel.setVisible(true);
|
||||
} else {
|
||||
configPanel.setVisible(false);
|
||||
}
|
||||
repaint();
|
||||
|
||||
} else if (source == showNetworkItem) {
|
||||
hideNetwork = !showNetworkItem.isSelected();
|
||||
repaint();
|
||||
|
||||
} else if (source == resetNetworkItem) {
|
||||
for(MapNode n : getNodeList()) {
|
||||
n.node.clearLinks();
|
||||
}
|
||||
repaint();
|
||||
} else if (isMap && source == backgroundItem) {
|
||||
showBackground = mapImage != null && backgroundItem.isSelected();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateNodeLayout() {
|
||||
MapNode[] nodes = getNodeList();
|
||||
for (MapNode n : nodes) {
|
||||
|
||||
// Attract connected nodes
|
||||
for(int i = 0, jn = n.node.getLinkCount(); i < jn; i++) {
|
||||
Link link = n.node.getLink(i);
|
||||
MapNode n2 = addMapNode(link.node);
|
||||
double vx = n2.x - n.x;
|
||||
double vy = n2.y - n.y;
|
||||
double dist = Math.sqrt(vx * vx + vy * vy);
|
||||
dist = dist == 0 ? 0.00001 : dist;
|
||||
double etx = link.getETX() * etxFactor;
|
||||
if (etx > 5) etx = 5;
|
||||
double factor = (etx * layoutAttract - dist) / (dist * 3);
|
||||
double dx = factor * vx;
|
||||
double dy = factor * vy;
|
||||
|
||||
n2.dx += dx;
|
||||
n2.dy += dy;
|
||||
n.dx -= dx;
|
||||
n.dy -= dy;
|
||||
}
|
||||
|
||||
// Repel nodes that are too close
|
||||
double dx = 0, dy = 0;
|
||||
for (MapNode n2 : nodes) {
|
||||
if (n == n2) {
|
||||
continue;
|
||||
}
|
||||
double vx = n.x - n2.x;
|
||||
double vy = n.y - n2.y;
|
||||
double dist = vx * vx + vy * vy;
|
||||
if (dist == 0) {
|
||||
dx += Math.random() * 5;
|
||||
dy += Math.random() * 5;
|
||||
} else if (dist < layoutRepel * layoutRepel) {
|
||||
dx += vx / dist;
|
||||
dy += vy / dist;
|
||||
}
|
||||
}
|
||||
double dist = dx * dx + dy * dy;
|
||||
if (dist > 0) {
|
||||
dist = Math.sqrt(dist) / 2;
|
||||
n.dx += dx / dist;
|
||||
n.dy += dy / dist;
|
||||
}
|
||||
|
||||
n.dy += layoutGravity;
|
||||
}
|
||||
|
||||
// Update the node positions
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
for(MapNode n : nodes) {
|
||||
if (!n.hasFixedLocation && n != draggedNode) {
|
||||
n.x += Math.max(-5, Math.min(5, n.dx));
|
||||
n.y += Math.max(-5, Math.min(5, n.dy));
|
||||
if (n.x < 0) {
|
||||
n.x = 0;
|
||||
} else if (n.x > width) {
|
||||
n.x = width;
|
||||
}
|
||||
if (n.y < 0) {
|
||||
n.y = 0;
|
||||
} else if (n.y > height) {
|
||||
n.y = height;
|
||||
}
|
||||
}
|
||||
n.dx /= 2;
|
||||
n.dy /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateConfigLayout() {
|
||||
configPanel.setLocation(getWidth() - configPanel.getWidth() - 10,
|
||||
getHeight() - configPanel.getHeight() - 10);
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Mouselistener
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
private MapNode getNodeAt(int mx, int my) {
|
||||
for(MapNode n : getNodeList()) {
|
||||
if (mx >= (n.x - delta)
|
||||
&& mx <= (n.x + delta)
|
||||
&& my >= (n.y - delta)
|
||||
&& my <= (n.y + delta)) {
|
||||
return n;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
int mx = e.getX();
|
||||
int my = e.getY();
|
||||
if (e.getButton() == MouseEvent.BUTTON1) {
|
||||
MapNode node = getNodeAt(mx, my);
|
||||
if (node != selectedNode) {
|
||||
server.selectNodes(node == null ? null : new Node[] { node.node });
|
||||
}
|
||||
}
|
||||
showPopup(e);
|
||||
}
|
||||
|
||||
public void mousePressed(MouseEvent e) {
|
||||
if (e.getButton() == MouseEvent.BUTTON1) {
|
||||
MapNode aNode = getNodeAt(e.getX(), e.getY());
|
||||
if (aNode != selectedNode) {
|
||||
server.selectNodes(aNode != null ? new Node[] { aNode.node } : null);
|
||||
}
|
||||
draggedNode = aNode;
|
||||
draggedTime = System.currentTimeMillis();
|
||||
|
||||
} else if (selectedNode != null) {
|
||||
if (draggedTime < 0) {
|
||||
setCursor(Cursor.getDefaultCursor());
|
||||
draggedTime = 0;
|
||||
}
|
||||
selectedNode = draggedNode = null;
|
||||
server.selectNodes(null);
|
||||
}
|
||||
showPopup(e);
|
||||
}
|
||||
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (draggedNode != null && e.getButton() == MouseEvent.BUTTON1) {
|
||||
if ((draggedTime > 0) &&
|
||||
(System.currentTimeMillis() - draggedTime) < 300) {
|
||||
// Do not drag if mouse is only moved during click
|
||||
|
||||
} else {
|
||||
draggedNode.x = e.getX();
|
||||
draggedNode.y = e.getY();
|
||||
setCursor(Cursor.getDefaultCursor());
|
||||
draggedTime = 0;
|
||||
draggedNode = null;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
showPopup(e);
|
||||
}
|
||||
|
||||
private void showPopup(MouseEvent e) {
|
||||
if (e.isPopupTrigger()
|
||||
&& (e.getModifiers() & (MouseEvent.SHIFT_MASK|MouseEvent.CTRL_MASK)) == 0) {
|
||||
popupNode = getNodeAt(e.getX(), e.getY());
|
||||
if (!isMap) {
|
||||
lockedItem.setEnabled(popupNode != null);
|
||||
lockedItem.setSelected(popupNode != null ? popupNode.hasFixedLocation : false);
|
||||
}
|
||||
popupMenu.show(this, e.getX(), e.getY());
|
||||
}
|
||||
}
|
||||
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
}
|
||||
|
||||
public void mouseExited(MouseEvent e) {
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// MouseMotion
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
if (draggedNode == null) {
|
||||
// Do nothing
|
||||
|
||||
} else if (draggedTime > 0) {
|
||||
if ((System.currentTimeMillis() - draggedTime) > 300) {
|
||||
// No mouse click, time to drag the node
|
||||
draggedTime = -1;
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
|
||||
}
|
||||
|
||||
} else if (VISUAL_DRAG /* && dragItem.isSelected() */) {
|
||||
draggedNode.x = e.getX();
|
||||
draggedNode.y = e.getY();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// MapNode
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
private static class MapNode {
|
||||
|
||||
public final Node node;
|
||||
public int x;
|
||||
public int y;
|
||||
public double dx;
|
||||
public double dy;
|
||||
public boolean hasFixedLocation;
|
||||
public boolean isSelected;
|
||||
public String message;
|
||||
|
||||
MapNode(MapPanel panel, Node node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
public void paint(Graphics g, int x, int y) {
|
||||
final int od = 3;
|
||||
g.setColor(Color.black);
|
||||
g.drawString(node.getID(), x + od * 2 + 3, y + 4);
|
||||
if (hasFixedLocation) {
|
||||
g.setColor(Color.red);
|
||||
}
|
||||
g.fillOval(x - od, y - od, od * 2 + 1, od * 2 + 1);
|
||||
}
|
||||
|
||||
} // end of inner class MapNode
|
||||
|
||||
|
||||
@Override
|
||||
public void updateConfig(Properties config) {
|
||||
if (isMap) {
|
||||
for (MapNode n : getNodeList()) {
|
||||
config.put(n.node.getID(), "" + n.x + ',' + n.y);
|
||||
}
|
||||
} else {
|
||||
for (MapNode n : getNodeList()) {
|
||||
if (n.hasFixedLocation) {
|
||||
config.put("collect.map." + n.node.getID(), "" + n.x + ',' + n.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
* Copyright (c) 2010, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: NodeControl.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* NodeControl
|
||||
*
|
||||
* Authors : Niclas Finne
|
||||
* Created : 27 sep 2010
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package se.sics.contiki.collect.gui;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JFormattedTextField;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSeparator;
|
||||
import javax.swing.JTextPane;
|
||||
import javax.swing.border.LineBorder;
|
||||
|
||||
import se.sics.contiki.collect.CollectServer;
|
||||
import se.sics.contiki.collect.Node;
|
||||
import se.sics.contiki.collect.SensorData;
|
||||
import se.sics.contiki.collect.Visualizer;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class NodeControl implements Visualizer {
|
||||
|
||||
private final static String SET_TIME_COMMAND = "time %TIME% | null";
|
||||
|
||||
private final CollectServer server;
|
||||
private final String category;
|
||||
private final JPanel panel;
|
||||
private final JLabel statusLabel;
|
||||
private final JSeparator statusSeparator;
|
||||
|
||||
public NodeControl(CollectServer server, String category) {
|
||||
this.server = server;
|
||||
this.category = category;
|
||||
this.panel = new JPanel(new BorderLayout());
|
||||
|
||||
final JFormattedTextField intervalField = new JFormattedTextField(new Integer(60));
|
||||
final JFormattedTextField randomField = new JFormattedTextField(new Integer(60));
|
||||
final JFormattedTextField reportsField = new JFormattedTextField(new Integer(0));
|
||||
final JFormattedTextField rexmitsField = new JFormattedTextField(new Integer(31));
|
||||
statusLabel = new JLabel("", JLabel.CENTER);
|
||||
statusLabel.setOpaque(true);
|
||||
statusLabel.setBackground(Color.white);
|
||||
statusLabel.setBorder(LineBorder.createBlackLineBorder());
|
||||
statusLabel.setVisible(false);
|
||||
statusSeparator = new JSeparator();
|
||||
statusSeparator.setVisible(false);
|
||||
|
||||
JButton stopButton = createCommandButton("Send stop to nodes", "netcmd killall");
|
||||
|
||||
JButton sendButton = new JButton("Send command to nodes");
|
||||
sendButton.addActionListener(new ActionListener() {
|
||||
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
int interval = (Integer)intervalField.getValue();
|
||||
int random = (Integer)randomField.getValue();
|
||||
int reports = (Integer)reportsField.getValue();
|
||||
int rexmits = (Integer)rexmitsField.getValue();
|
||||
|
||||
sendCommand("netcmd { repeat " + reports + " " + interval
|
||||
+ " { randwait " + random + " collect-view-data | send " + rexmits + " } }");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
JButton collectButton = createCommandButton("Start Collect",
|
||||
"~K", "killall",
|
||||
"mac 0", SET_TIME_COMMAND,
|
||||
"collect | timestamp | binprint &");
|
||||
JButton stopCollectButton = createCommandButton("Stop Collect", "~K", "killall");
|
||||
|
||||
JPanel controlPanel = new JPanel(new GridBagLayout());
|
||||
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.weightx = 0.5;
|
||||
c.insets.left = c.insets.right = c.insets.bottom = 3;
|
||||
c.anchor = GridBagConstraints.WEST;
|
||||
c.gridy = 0;
|
||||
|
||||
c.gridwidth = 3;
|
||||
controlPanel.add(statusLabel, c);
|
||||
c.gridy++;
|
||||
controlPanel.add(statusSeparator, c);
|
||||
c.insets.top = 10;
|
||||
|
||||
c.gridy++;
|
||||
c.gridwidth = 1;
|
||||
controlPanel.add(new JLabel("Program Connected Nodes", JLabel.RIGHT), c);
|
||||
c.gridwidth = 3;
|
||||
c.fill = GridBagConstraints.NONE;
|
||||
controlPanel.add(new JButton(server.getMoteProgramAction()), c);
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
|
||||
c.gridy++;
|
||||
c.gridwidth = 1;
|
||||
controlPanel.add(new JLabel("Serial Connection", JLabel.RIGHT), c);
|
||||
c.gridwidth = 3;
|
||||
c.fill = GridBagConstraints.NONE;
|
||||
controlPanel.add(new JButton(server.getConnectSerialAction()), c);
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
|
||||
c.gridy++;
|
||||
controlPanel.add(new JSeparator(), c);
|
||||
|
||||
c.gridy++;
|
||||
c.gridwidth = 1;
|
||||
controlPanel.add(new JLabel("Base Station Control", JLabel.RIGHT), c);
|
||||
c.gridwidth = 2;
|
||||
JPanel basePanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 5));
|
||||
basePanel.add(collectButton);
|
||||
basePanel.add(stopCollectButton);
|
||||
c.insets.left -= 5;
|
||||
controlPanel.add(basePanel, c);
|
||||
c.insets.left += 5;
|
||||
|
||||
c.gridy++;
|
||||
c.gridwidth = 3;
|
||||
controlPanel.add(new JSeparator(), c);
|
||||
|
||||
c.gridy++;
|
||||
JLabel label = new JLabel("Collect Settings", JLabel.CENTER);
|
||||
controlPanel.add(label, c);
|
||||
c.gridwidth = 1;
|
||||
|
||||
c.gridy++;
|
||||
controlPanel.add(label = new JLabel("Report interval", JLabel.RIGHT), c);
|
||||
label.setLabelFor(intervalField);
|
||||
controlPanel.add(intervalField, c);
|
||||
controlPanel.add(new JLabel("seconds"), c);
|
||||
|
||||
c.insets.top = 3;
|
||||
c.gridy++;
|
||||
controlPanel.add(label = new JLabel("Report randomness", JLabel.RIGHT), c);
|
||||
label.setLabelFor(randomField);
|
||||
controlPanel.add(randomField, c);
|
||||
controlPanel.add(new JLabel("seconds"), c);
|
||||
|
||||
c.gridy++;
|
||||
controlPanel.add(label = new JLabel("Hop-by-hop retransmissions", JLabel.RIGHT), c);
|
||||
label.setLabelFor(rexmitsField);
|
||||
controlPanel.add(rexmitsField, c);
|
||||
controlPanel.add(new JLabel("retransmissions (0 - 31)"), c);
|
||||
|
||||
c.gridy++;
|
||||
controlPanel.add(new JLabel("Number of reports", JLabel.RIGHT), c);
|
||||
label.setLabelFor(reportsField);
|
||||
controlPanel.add(reportsField, c);
|
||||
controlPanel.add(new JLabel("(0 = report forever)"), c);
|
||||
|
||||
c.gridy++;
|
||||
c.gridwidth = 3;
|
||||
c.insets.bottom = 10;
|
||||
JPanel nodePanel = new JPanel();
|
||||
nodePanel.add(sendButton);
|
||||
nodePanel.add(stopButton);
|
||||
controlPanel.add(nodePanel, c);
|
||||
|
||||
c.gridy++;
|
||||
controlPanel.add(new JSeparator(), c);
|
||||
panel.add(controlPanel, BorderLayout.NORTH);
|
||||
|
||||
JTextPane helpPane = new JTextPane();
|
||||
helpPane.setContentType("text/html");
|
||||
helpPane.setEditable(false);
|
||||
helpPane.setText("<html>" +
|
||||
"<h3>Quick Startup Instructions</h3>" +
|
||||
"<lu>" +
|
||||
"<li> Connect nodes to USB. Press the <strong>Program Nodes...</strong> button." +
|
||||
"<li> Disconnect all except one node. " +
|
||||
"Press the <strong>Connect to Serial</strong> button." +
|
||||
"<li> Press the <strong>Start Collect</strong> button." +
|
||||
"<li> Press the <strong>Send command to nodes</strong> button." +
|
||||
"</lu>" +
|
||||
"</html>");
|
||||
helpPane.setBackground(panel.getBackground());
|
||||
JScrollPane helpScroll = new JScrollPane(helpPane,
|
||||
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
|
||||
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
helpScroll.setBorder(BorderFactory.createEmptyBorder(3, 10, 10, 10));
|
||||
panel.add(helpScroll, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
private JButton createCommandButton(String name, final String... command) {
|
||||
JButton button = new JButton(name);
|
||||
button.addActionListener(new ActionListener() {
|
||||
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
try {
|
||||
// TODO Should use separate thread to send commands
|
||||
panel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
for(int i = 0, n = command.length; i < n; i++) {
|
||||
if (i > 0) {
|
||||
try {
|
||||
// Do not send multiple commands too fast
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e1) {
|
||||
}
|
||||
}
|
||||
String cmd = command[i];
|
||||
if (cmd == SET_TIME_COMMAND) {
|
||||
cmd = "time " + (System.currentTimeMillis() / 1000) + " | null";
|
||||
}
|
||||
if (!sendCommand(cmd)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
panel.setCursor(Cursor.getDefaultCursor());
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
return button;
|
||||
}
|
||||
|
||||
protected boolean sendCommand(String command) {
|
||||
if (server.sendToNode(command)) {
|
||||
setStatus("Sent command '" + command + "'", false);
|
||||
return true;
|
||||
}
|
||||
setStatus("Failed to send command. No serial connection.", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void setStatus(String text, boolean isWarning) {
|
||||
statusLabel.setForeground(isWarning ? Color.red : Color.black);
|
||||
statusLabel.setText(text);
|
||||
statusLabel.setVisible(true);
|
||||
statusSeparator.setVisible(true);
|
||||
}
|
||||
|
||||
public String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return "Node Control";
|
||||
}
|
||||
|
||||
public Component getPanel() {
|
||||
return panel;
|
||||
}
|
||||
|
||||
public void nodesSelected(Node[] node) {
|
||||
}
|
||||
|
||||
public void nodeAdded(Node node) {
|
||||
}
|
||||
|
||||
public void nodeDataReceived(SensorData sensorData) {
|
||||
}
|
||||
|
||||
public void clearNodeData() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,651 @@
|
|||
/*
|
||||
* Copyright (c) 2010, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: NodeInfoPanel.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* NodeInfoPanel
|
||||
*
|
||||
* Authors : Joakim Eriksson, Niclas Finne
|
||||
* Created : 6 sep 2010
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package se.sics.contiki.collect.gui;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Font;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.Comparator;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.JCheckBoxMenuItem;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import javax.swing.table.JTableHeader;
|
||||
import javax.swing.table.TableCellRenderer;
|
||||
import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
|
||||
import se.sics.contiki.collect.CollectServer;
|
||||
import se.sics.contiki.collect.Configurable;
|
||||
import se.sics.contiki.collect.Node;
|
||||
import se.sics.contiki.collect.SensorData;
|
||||
import se.sics.contiki.collect.SensorInfo;
|
||||
import se.sics.contiki.collect.Visualizer;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class NodeInfoPanel extends JPanel implements Visualizer, Configurable {
|
||||
|
||||
private static final long serialVersionUID = -1060893468047793431L;
|
||||
|
||||
private static Comparator<Number> NUMBER_COMPARATOR = new Comparator<Number>() {
|
||||
|
||||
public int compare(Number o1, Number o2) {
|
||||
double v1 = o1.doubleValue();
|
||||
double v2 = o2.doubleValue();
|
||||
return (v1 < v2 ? -1 : (v1 == v2 ? 0 : 1));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private final CollectServer server;
|
||||
private final String category;
|
||||
private final JTable table;
|
||||
private final NodeModel nodeModel;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public NodeInfoPanel(CollectServer server, String category) {
|
||||
super(new BorderLayout());
|
||||
this.server = server;
|
||||
this.category = category;
|
||||
|
||||
TableData[] columns = new TableData[] {
|
||||
new TableData("Node", Node.class) {
|
||||
public Object getValue(Node node) {
|
||||
return node;
|
||||
}
|
||||
},
|
||||
new TableData("Received", "Packets Received", Number.class) {
|
||||
public Object getValue(Node node) {
|
||||
return node.getSensorDataAggregator().getPacketCount();
|
||||
}
|
||||
},
|
||||
new TableData("Dups", "Duplicate Packets Received", Number.class) {
|
||||
public Object getValue(Node node) {
|
||||
return node.getSensorDataAggregator().getDuplicateCount();
|
||||
}
|
||||
},
|
||||
new TableData("Lost", "Estimated Lost Packets", Number.class) {
|
||||
public Object getValue(Node node) {
|
||||
return node.getSensorDataAggregator().getEstimatedLostCount();
|
||||
}
|
||||
},
|
||||
new TableData("Hops", "Average Hops to Sink", Double.class) {
|
||||
public Object getValue(Node node) {
|
||||
return node.getSensorDataAggregator().getAverageValue(SensorData.HOPS);
|
||||
}
|
||||
},
|
||||
new TableData("Rtmetric", "Average Routing Metric", Double.class) {
|
||||
public Object getValue(Node node) {
|
||||
return node.getSensorDataAggregator().getAverageRtmetric();
|
||||
}
|
||||
},
|
||||
new TableData("ETX", "Average ETX to Next Hop", Double.class) {
|
||||
public Object getValue(Node node) {
|
||||
return node.getSensorDataAggregator().getAverageBestNeighborETX();
|
||||
}
|
||||
},
|
||||
new TableData("Churn", "Next Hop Change Count", Number.class) {
|
||||
public Object getValue(Node node) {
|
||||
return node.getSensorDataAggregator().getNextHopChangeCount();
|
||||
}
|
||||
},
|
||||
new TableData("Beacon Interval", "Average Beacon Interval", Long.class) {
|
||||
public Object getValue(Node node) {
|
||||
return (long)(node.getSensorDataAggregator().getAverageValue(SensorData.BEACON_INTERVAL) * 1000);
|
||||
}
|
||||
},
|
||||
|
||||
new TableData("Reboots", "Estimated Node Restart Count", Number.class) {
|
||||
public Object getValue(Node node) {
|
||||
return node.getSensorDataAggregator().getEstimatedRestarts();
|
||||
}
|
||||
},
|
||||
|
||||
// Power
|
||||
new TableData("CPU Power", "Average CPU Power Consumption", Double.class) {
|
||||
public Object getValue(Node node) {
|
||||
return node.getSensorDataAggregator().getCPUPower();
|
||||
}
|
||||
},
|
||||
new TableData("LPM Power", "Average LPM Power Consumption", Double.class) {
|
||||
public Object getValue(Node node) {
|
||||
return node.getSensorDataAggregator().getLPMPower();
|
||||
}
|
||||
},
|
||||
new TableData("Listen Power", "Average Radio Listen Power Consumption", Double.class) {
|
||||
public Object getValue(Node node) {
|
||||
return node.getSensorDataAggregator().getListenPower();
|
||||
}
|
||||
},
|
||||
new TableData("Transmit Power", "Average Radio Transmit Power Consumption", Double.class) {
|
||||
public Object getValue(Node node) {
|
||||
return node.getSensorDataAggregator().getTransmitPower();
|
||||
}
|
||||
},
|
||||
new TableData("Power", "Average Power Consumption", Double.class) {
|
||||
public Object getValue(Node node) {
|
||||
return node.getSensorDataAggregator().getAveragePower();
|
||||
}
|
||||
},
|
||||
new TableData("On-time", "Power Measure Time", Long.class) {
|
||||
public Object getValue(Node node) {
|
||||
return node.getSensorDataAggregator().getPowerMeasureTime();
|
||||
}
|
||||
},
|
||||
|
||||
new TableData("Listen Duty Cycle", "Average Radio Listen Duty Cycle (%)", Double.class) {
|
||||
public Object getValue(Node node) {
|
||||
return 100 * node.getSensorDataAggregator().getAverageDutyCycle(SensorInfo.TIME_LISTEN);
|
||||
}
|
||||
},
|
||||
new TableData("Transmit Duty Cycle", "Average Radio Transmit Duty Cycle (%)", Double.class) {
|
||||
public Object getValue(Node node) {
|
||||
return 100 * node.getSensorDataAggregator().getAverageDutyCycle(SensorInfo.TIME_TRANSMIT);
|
||||
}
|
||||
},
|
||||
|
||||
// Inter-packet times
|
||||
new TableData("Avg Inter-packet Time", Long.class) {
|
||||
public Object getValue(Node node) {
|
||||
return node.getSensorDataAggregator().getAveragePeriod();
|
||||
}
|
||||
},
|
||||
new TableData("Min Inter-packet Time", Long.class) {
|
||||
public Object getValue(Node node) {
|
||||
return node.getSensorDataAggregator().getShortestPeriod();
|
||||
}
|
||||
},
|
||||
new TableData("Max Inter-packet Time", Long.class) {
|
||||
public Object getValue(Node node) {
|
||||
return node.getSensorDataAggregator().getLongestPeriod();
|
||||
}
|
||||
}
|
||||
};
|
||||
nodeModel = new NodeModel(columns);
|
||||
table = new JTable(nodeModel) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private Font fontForAverage;
|
||||
|
||||
protected JTableHeader createDefaultTableHeader() {
|
||||
return new JTableHeader(columnModel) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public String getToolTipText(MouseEvent e) {
|
||||
int index = columnModel.getColumnIndexAtX(e.getX());
|
||||
int modelIndex = index < 0 ? index : columnModel.getColumn(index).getModelIndex();
|
||||
return modelIndex < 0 ? null : nodeModel.getColumnToolTip(modelIndex);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Component prepareRenderer(TableCellRenderer renderer, int rowIndex, int vColIndex) {
|
||||
Component c = super.prepareRenderer(renderer, rowIndex, vColIndex);
|
||||
int row = convertRowIndexToModel(rowIndex);
|
||||
if (row == nodeModel.getRowCount() - 1) {
|
||||
if (fontForAverage == null) {
|
||||
fontForAverage = c.getFont().deriveFont(Font.BOLD);
|
||||
}
|
||||
// Last line is average
|
||||
c.setFont(fontForAverage);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Do not sort column when clicking between the columns (resizing)
|
||||
TableRowSorter<NodeModel> sorter = new TableRowSorter<NodeModel>(nodeModel) {
|
||||
public void toggleSortOrder(int column) {
|
||||
if(table.getTableHeader().getCursor().getType() != Cursor.E_RESIZE_CURSOR) {
|
||||
super.toggleSortOrder(column);
|
||||
}
|
||||
}
|
||||
};
|
||||
for(int c = 0; c < columns.length; c++) {
|
||||
if (columns[c].dataClass == Number.class) {
|
||||
sorter.setComparator(c, NUMBER_COMPARATOR);
|
||||
}
|
||||
}
|
||||
table.setRowSorter(sorter);
|
||||
// Pack the column when double clicking between columns (resizing)
|
||||
table.getTableHeader().addMouseListener(new MouseAdapter() {
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if(e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton(e) &&
|
||||
table.getTableHeader().getCursor().getType() == Cursor.E_RESIZE_CURSOR) {
|
||||
int index = table.getColumnModel().getColumnIndexAtX(e.getX() - 3);
|
||||
if (index >= 0) {
|
||||
packColumn(table, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add right aligned renderer for node name
|
||||
DefaultTableCellRenderer renderer = new DefaultTableCellRenderer();
|
||||
renderer.setHorizontalAlignment(JLabel.RIGHT);
|
||||
table.setDefaultRenderer(Node.class, renderer);
|
||||
|
||||
// Add renderer for time
|
||||
renderer = new DefaultTableCellRenderer() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public void setValue(Object value) {
|
||||
long time = (Long) value;
|
||||
setText(time > 0 ? getTimeAsString(time) : null);
|
||||
}
|
||||
};
|
||||
renderer.setHorizontalAlignment(JLabel.RIGHT);
|
||||
table.setDefaultRenderer(Long.class, renderer);
|
||||
|
||||
// Add renderer for double
|
||||
renderer = new DefaultTableCellRenderer() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public void setValue(Object value) {
|
||||
if (value == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
double v = ((Number) value).doubleValue() + 0.0005;
|
||||
int dec = ((int)(v * 1000)) % 1000;
|
||||
setText((long)v + "." + (dec > 99 ? "" : "0") + (dec > 9 ? "" : "0") + dec);
|
||||
}
|
||||
}
|
||||
};
|
||||
renderer.setHorizontalAlignment(JLabel.RIGHT);
|
||||
table.setDefaultRenderer(Double.class, renderer);
|
||||
|
||||
// Add renderer for mixed integers and doubles
|
||||
renderer = new DefaultTableCellRenderer() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public void setValue(Object value) {
|
||||
if (value == null) {
|
||||
setText(null);
|
||||
} else if (value instanceof Integer) {
|
||||
setText(value.toString());
|
||||
} else {
|
||||
double v = ((Number) value).doubleValue() + 0.0005;
|
||||
int dec = ((int)(v * 1000)) % 1000;
|
||||
setText((long)v + "." + (dec > 99 ? "" : "0") + (dec > 9 ? "" : "0") + dec);
|
||||
}
|
||||
}
|
||||
};
|
||||
renderer.setHorizontalAlignment(JLabel.RIGHT);
|
||||
table.setDefaultRenderer(Number.class, renderer);
|
||||
|
||||
table.setFillsViewportHeight(true);
|
||||
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
|
||||
for (int i = 0, n = table.getColumnCount(); i < n; i++) {
|
||||
packColumn(table, i);
|
||||
}
|
||||
|
||||
String savedColumnData = server.getConfig("collect.nodeinfo.table");
|
||||
if (savedColumnData != null) {
|
||||
String[] columnList = savedColumnData.split("[ ,]");
|
||||
for(int i = 1; i < columns.length; i++) {
|
||||
columns[i].setVisible(false);
|
||||
}
|
||||
for(int i = 0; i < columnList.length; i++) {
|
||||
int c = Integer.parseInt(columnList[i]);
|
||||
int index = table.convertColumnIndexToView(c);
|
||||
if (index >= 0) {
|
||||
table.getColumnModel().moveColumn(index, i);
|
||||
}
|
||||
columns[c].setVisible(true);
|
||||
}
|
||||
}
|
||||
JPopupMenu popupMenu = new JPopupMenu();
|
||||
// The first column (the node name) should always be visible.
|
||||
for(int i = 1; i < columns.length; i++) {
|
||||
popupMenu.add(new JCheckBoxMenuItem(columns[i].init(table, i)));
|
||||
}
|
||||
table.setComponentPopupMenu(popupMenu);
|
||||
add(new JScrollPane(table), BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
public void updateConfig(Properties config) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for(int i = 0, n = table.getColumnCount(); i < n; i++) {
|
||||
int index = table.convertColumnIndexToModel(i);
|
||||
if (index >= 0) {
|
||||
if (sb.length() > 0) {
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append(index);
|
||||
}
|
||||
}
|
||||
config.setProperty("collect.nodeinfo.table", sb.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getPanel() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return "Node Info";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodeAdded(Node node) {
|
||||
nodeModel.setNodes(server.getNodes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodeDataReceived(SensorData sensorData) {
|
||||
nodeModel.updateNode(sensorData.getNode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearNodeData() {
|
||||
nodeModel.setNodes(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodesSelected(Node[] nodes) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean visible) {
|
||||
nodeModel.setNodes(visible ? server.getNodes() : null);
|
||||
super.setVisible(visible);
|
||||
}
|
||||
|
||||
private String getTimeAsString(long time) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
time /= 1000;
|
||||
if (time > 24 * 60 * 60) {
|
||||
long days = time / (24 * 60 * 60);
|
||||
sb.append(days).append(days > 1 ? " days, " : " day, ");
|
||||
time -= days * 24 * 60 * 60;
|
||||
}
|
||||
if (time > 60 * 60) {
|
||||
long hours = time / (60 * 60);
|
||||
sb.append(hours).append(hours > 1 ? " hours, " : " hour, ");
|
||||
time -= hours * 60 * 60;
|
||||
}
|
||||
long sec = time % 60;
|
||||
sb.append(time / 60).append(" min, ");
|
||||
if (sec < 10) {
|
||||
sb.append('0');
|
||||
}
|
||||
sb.append(sec).append(" sec");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void packColumn(JTable table, int columnIndex) {
|
||||
TableColumn tableColumn = table.getColumnModel().getColumn(columnIndex);
|
||||
Object value = tableColumn.getHeaderValue();
|
||||
TableCellRenderer columnRenderer = tableColumn.getHeaderRenderer();
|
||||
if (columnRenderer == null) {
|
||||
columnRenderer = table.getTableHeader().getDefaultRenderer();
|
||||
}
|
||||
Component c = columnRenderer.getTableCellRendererComponent(table, value, false, false, -1, columnIndex);
|
||||
int width = c.getPreferredSize().width + 6;
|
||||
int intercellSpacing = table.getIntercellSpacing().width;
|
||||
for(int i = 0, n = table.getRowCount(); i < n; i++) {
|
||||
TableCellRenderer cellRenderer = table.getCellRenderer(i, columnIndex);
|
||||
value = table.getValueAt(i, columnIndex);
|
||||
c = cellRenderer.getTableCellRendererComponent(table, value, false, false, i, columnIndex);
|
||||
int w = c.getPreferredSize().width + intercellSpacing + 2;
|
||||
if (w > width) {
|
||||
width = w;
|
||||
}
|
||||
}
|
||||
table.getTableHeader().setResizingColumn(tableColumn);
|
||||
tableColumn.setWidth(width);
|
||||
tableColumn.setPreferredWidth(width);
|
||||
}
|
||||
|
||||
private static class NodeModel extends AbstractTableModel {
|
||||
|
||||
private static final long serialVersionUID = 1692207305977527004L;
|
||||
|
||||
private final TableData[] columns;
|
||||
private Node[] nodes;
|
||||
|
||||
public NodeModel(TableData[] columns) {
|
||||
this.columns = columns;
|
||||
}
|
||||
|
||||
public void recalculateAverage() {
|
||||
for(TableData td : columns) {
|
||||
td.clearAverageCache();
|
||||
}
|
||||
int row = getRowCount() - 1;
|
||||
fireTableRowsUpdated(row, row);
|
||||
}
|
||||
|
||||
public Object getValueAt(int row, int col) {
|
||||
int count = nodes == null ? 0 : nodes.length;
|
||||
if (row == count) {
|
||||
return columns[col].getAverageValue(nodes);
|
||||
}
|
||||
return columns[col].getValue(nodes[row]);
|
||||
}
|
||||
|
||||
public Class<?> getColumnClass(int col) {
|
||||
return columns[col].dataClass;
|
||||
}
|
||||
|
||||
public String getColumnName(int col) {
|
||||
return columns[col].name;
|
||||
}
|
||||
|
||||
public String getColumnToolTip(int col) {
|
||||
Object v = columns[col].getValue(TableData.SHORT_DESCRIPTION);
|
||||
return v == null ? null : v.toString();
|
||||
}
|
||||
|
||||
public int getColumnCount() {
|
||||
return columns.length;
|
||||
}
|
||||
|
||||
public int getRowCount() {
|
||||
return (nodes == null ? 0 : nodes.length) + 1;
|
||||
}
|
||||
|
||||
public void setNodes(Node[] nodes) {
|
||||
if (this.nodes != null && this.nodes.length > 0) {
|
||||
fireTableRowsDeleted(0, this.nodes.length - 1);
|
||||
}
|
||||
this.nodes = nodes;
|
||||
if (this.nodes != null && this.nodes.length > 0) {
|
||||
fireTableRowsInserted(0, this.nodes.length - 1);
|
||||
}
|
||||
recalculateAverage();
|
||||
}
|
||||
|
||||
public void updateNode(Node node) {
|
||||
if (this.nodes != null) {
|
||||
for(int row = 0; row < this.nodes.length; row++) {
|
||||
if (this.nodes[row] == node) {
|
||||
fireTableRowsUpdated(row, row);
|
||||
recalculateAverage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static abstract class TableData extends AbstractAction {
|
||||
private static final long serialVersionUID = -3045755073722516926L;
|
||||
|
||||
private final static Node AVERAGE_NODE = new Node("99999999.9", "Avg");
|
||||
|
||||
public final String name;
|
||||
public final Class<?> dataClass;
|
||||
|
||||
private JTable table;
|
||||
private TableColumn tableColumn;
|
||||
private int modelIndex = -1;
|
||||
|
||||
private Object averageCache;
|
||||
|
||||
protected TableData(String name, Class<?> dataClass) {
|
||||
this(name, name, dataClass);
|
||||
}
|
||||
|
||||
protected TableData(String name, String description, Class<?> dataClass) {
|
||||
super(name);
|
||||
this.name = name;
|
||||
this.dataClass = dataClass;
|
||||
putValue(SHORT_DESCRIPTION, description);
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
TableData init(JTable table, int modelIndex) {
|
||||
this.table = table;
|
||||
this.modelIndex = modelIndex;
|
||||
if (!isVisible()) {
|
||||
// The column should initially be hidden
|
||||
setColumnVisible(false);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return Boolean.TRUE.equals(getValue(SELECTED_KEY));
|
||||
}
|
||||
|
||||
public TableData setVisible(boolean isVisible) {
|
||||
putValue(SELECTED_KEY, isVisible);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
if (modelIndex >= 0) {
|
||||
setColumnVisible(isVisible());
|
||||
}
|
||||
}
|
||||
|
||||
private void setColumnVisible(boolean isVisible) {
|
||||
if (isVisible) {
|
||||
if (tableColumn != null) {
|
||||
int count = table.getColumnCount();
|
||||
table.addColumn(tableColumn);
|
||||
tableColumn = null;
|
||||
packColumn(table, count);
|
||||
|
||||
int newIndex = 0;
|
||||
for(int i = 0; i < modelIndex; i++) {
|
||||
if (table.convertColumnIndexToView(i) >= 0) {
|
||||
// The new column should be after this visible column
|
||||
newIndex++;
|
||||
}
|
||||
}
|
||||
if (newIndex < count) {
|
||||
table.getColumnModel().moveColumn(count, newIndex);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int columnIndex = table.convertColumnIndexToView(modelIndex);
|
||||
if (columnIndex >= 0 ) {
|
||||
tableColumn = table.getColumnModel().getColumn(columnIndex);
|
||||
table.removeColumn(tableColumn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final Object getAverageValue(Node[] nodes) {
|
||||
Object tmp = averageCache;
|
||||
if (tmp != null) {
|
||||
return tmp;
|
||||
}
|
||||
|
||||
if (dataClass == Long.class || dataClass == Double.class
|
||||
|| dataClass == Number.class) {
|
||||
double average = 0.0;
|
||||
if (nodes != null && nodes.length > 0) {
|
||||
int count = 0;
|
||||
for(Node node : nodes) {
|
||||
if (node.getSensorDataAggregator().getDataCount() > 0) {
|
||||
average += ((Number) getValue(node)).doubleValue();
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (count > 0) {
|
||||
average = average / count;
|
||||
}
|
||||
}
|
||||
if (dataClass == Long.class) {
|
||||
tmp = (long) (average + 0.5);
|
||||
} else {
|
||||
tmp = average;
|
||||
}
|
||||
} else if (dataClass == Node.class) {
|
||||
tmp = AVERAGE_NODE;
|
||||
}
|
||||
averageCache = tmp;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
public final void clearAverageCache() {
|
||||
averageCache = null;
|
||||
}
|
||||
|
||||
public abstract Object getValue(Node node);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
* Copyright (c) 2008, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: SerialConsole.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* SerialConsole
|
||||
*
|
||||
* Authors : Joakim Eriksson, Niclas Finne
|
||||
* Created : 4 jul 2008
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package se.sics.contiki.collect.gui;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.SwingUtilities;
|
||||
import se.sics.contiki.collect.CollectServer;
|
||||
import se.sics.contiki.collect.Node;
|
||||
import se.sics.contiki.collect.SensorData;
|
||||
import se.sics.contiki.collect.Visualizer;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class SerialConsole implements Visualizer {
|
||||
|
||||
private final CollectServer server;
|
||||
private final String category;
|
||||
private JPanel panel;
|
||||
private JTextArea logArea;
|
||||
private JTextField commandField;
|
||||
private String[] history = new String[50];
|
||||
private int historyPos = 0;
|
||||
private int historyCount = 0;
|
||||
|
||||
public SerialConsole(CollectServer server, String category) {
|
||||
this.server = server;
|
||||
this.category = category;
|
||||
panel = new JPanel(new BorderLayout());
|
||||
logArea = new JTextArea(4, 30);
|
||||
logArea.setEditable(false);
|
||||
panel.add(new JScrollPane(logArea), BorderLayout.CENTER);
|
||||
|
||||
JPopupMenu popupMenu = new JPopupMenu();
|
||||
JMenuItem clearItem = new JMenuItem("Clear");
|
||||
clearItem.addActionListener(new ActionListener() {
|
||||
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
logArea.setText("");
|
||||
}
|
||||
|
||||
});
|
||||
popupMenu.add(clearItem);
|
||||
logArea.setComponentPopupMenu(popupMenu);
|
||||
|
||||
commandField = new JTextField();
|
||||
commandField.addActionListener(new ActionListener() {
|
||||
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
String command = trim(commandField.getText());
|
||||
if (command != null) {
|
||||
try {
|
||||
int previous = historyCount - 1;
|
||||
if (previous < 0) previous += history.length;
|
||||
if (!command.equals(history[previous])) {
|
||||
history[historyCount] = command;
|
||||
historyCount = (historyCount + 1) % history.length;
|
||||
}
|
||||
historyPos = historyCount;
|
||||
if (SerialConsole.this.server.sendToNode(command)) {
|
||||
commandField.setText("");
|
||||
} else {
|
||||
addSerialData("*** failed to send command ***");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
System.err.println("could not send '" + command + "':");
|
||||
ex.printStackTrace();
|
||||
JOptionPane.showMessageDialog(panel,
|
||||
"could not send '" + command + "':\n"
|
||||
+ ex, "ERROR",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
} else {
|
||||
commandField.getToolkit().beep();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
commandField.addKeyListener(new KeyAdapter() {
|
||||
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
switch (e.getKeyCode()) {
|
||||
case KeyEvent.VK_UP: {
|
||||
int nextPos = (historyPos + history.length - 1) % history.length;
|
||||
if (nextPos == historyCount || history[nextPos] == null) {
|
||||
commandField.getToolkit().beep();
|
||||
} else {
|
||||
String cmd = trim(commandField.getText());
|
||||
if (cmd != null) {
|
||||
history[historyPos] = cmd;
|
||||
}
|
||||
historyPos = nextPos;
|
||||
commandField.setText(history[historyPos]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case KeyEvent.VK_DOWN: {
|
||||
int nextPos = (historyPos + 1) % history.length;
|
||||
if (nextPos == historyCount) {
|
||||
historyPos = nextPos;
|
||||
commandField.setText("");
|
||||
} else if (historyPos == historyCount || history[nextPos] == null) {
|
||||
commandField.getToolkit().beep();
|
||||
} else {
|
||||
String cmd = trim(commandField.getText());
|
||||
if (cmd != null) {
|
||||
history[historyPos] = cmd;
|
||||
}
|
||||
historyPos = nextPos;
|
||||
commandField.setText(history[historyPos]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
panel.add(commandField, BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getPanel() {
|
||||
return panel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return "Serial Console";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodeAdded(Node node) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodeDataReceived(SensorData sensorData) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearNodeData() {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodesSelected(Node[] node) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
public void addSerialData(final String text) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
String current = logArea.getText();
|
||||
int len = current.length();
|
||||
if (len > 4096) {
|
||||
current = current.substring(len - 4096);
|
||||
}
|
||||
current = len > 0 ? (current + '\n' + text) : text;
|
||||
logArea.setText(current);
|
||||
logArea.setCaretPosition(current.length());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String trim(String text) {
|
||||
return (text != null) && ((text = text.trim()).length() > 0) ? text : null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,329 @@
|
|||
/*
|
||||
* Copyright (c) 2008, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: TimeChartPanel.java,v 1.1 2010/11/03 14:53:05 adamdunkels Exp $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* TimeChartPanel
|
||||
*
|
||||
* Authors : Joakim Eriksson, Niclas Finne
|
||||
* Created : 3 jul 2008
|
||||
* Updated : $Date: 2010/11/03 14:53:05 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package se.sics.contiki.collect.gui;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.util.Date;
|
||||
import javax.swing.JPanel;
|
||||
import org.jfree.chart.ChartFactory;
|
||||
import org.jfree.chart.ChartPanel;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
|
||||
import org.jfree.data.time.Second;
|
||||
import org.jfree.data.time.TimeSeries;
|
||||
import org.jfree.data.time.TimeSeriesCollection;
|
||||
import se.sics.contiki.collect.CollectServer;
|
||||
import se.sics.contiki.collect.Node;
|
||||
import se.sics.contiki.collect.SensorData;
|
||||
import se.sics.contiki.collect.Visualizer;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public abstract class TimeChartPanel extends JPanel implements Visualizer {
|
||||
|
||||
private static final long serialVersionUID = -607864439709540641L;
|
||||
|
||||
protected final CollectServer server;
|
||||
protected final String category;
|
||||
protected final String title;
|
||||
protected final TimeSeriesCollection timeSeries;
|
||||
protected final JFreeChart chart;
|
||||
protected final ChartPanel chartPanel;
|
||||
|
||||
private Node[] selectedNodes;
|
||||
|
||||
private double minValue;
|
||||
private double maxValue;
|
||||
private int rangeTick = 0;
|
||||
private boolean hasGlobalRange;
|
||||
private int maxItemCount;
|
||||
|
||||
public TimeChartPanel(CollectServer server, String category, String title,
|
||||
String chartTitle, String timeAxisLabel, String valueAxisLabel) {
|
||||
super(new BorderLayout());
|
||||
this.server = server;
|
||||
this.category = category;
|
||||
this.title = title;
|
||||
this.timeSeries = new TimeSeriesCollection();
|
||||
this.chart = ChartFactory.createTimeSeriesChart(
|
||||
chartTitle, timeAxisLabel, valueAxisLabel, timeSeries,
|
||||
true, true, false
|
||||
);
|
||||
this.chartPanel = new ChartPanel(chart);
|
||||
this.chartPanel.setPreferredSize(new Dimension(500, 270));
|
||||
setBaseShapeVisible(true);
|
||||
setMaxItemCount(server.getDefaultMaxItemCount());
|
||||
add(chartPanel, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getPanel() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodeAdded(Node node) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodesSelected(Node[] nodes) {
|
||||
if (this.selectedNodes != nodes) {
|
||||
this.selectedNodes = nodes;
|
||||
if (isVisible()) {
|
||||
updateCharts();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodeDataReceived(SensorData data) {
|
||||
if (hasGlobalRange) {
|
||||
boolean update = false;
|
||||
if (minValue > maxValue) {
|
||||
update = true;
|
||||
} else {
|
||||
double value = getSensorDataValue(data);
|
||||
if (value < minValue) {
|
||||
minValue = value;
|
||||
update = true;
|
||||
}
|
||||
if (value > maxValue) {
|
||||
maxValue = value;
|
||||
update = true;
|
||||
}
|
||||
}
|
||||
if (update && isVisible()) {
|
||||
updateGlobalRange();
|
||||
}
|
||||
}
|
||||
if (isVisible() && selectedNodes != null && selectedNodes.length == timeSeries.getSeriesCount()) {
|
||||
Node node = data.getNode();
|
||||
for (int i = 0, n = selectedNodes.length; i < n; i++) {
|
||||
if (node == selectedNodes[i]) {
|
||||
TimeSeries series = timeSeries.getSeries(i);
|
||||
int groupSize = getGroupSize(node);
|
||||
if (groupSize > 1) {
|
||||
series.clear();
|
||||
updateSeries(series, node, groupSize);
|
||||
} else {
|
||||
series.addOrUpdate(new Second(new Date(data.getNodeTime())), getSensorDataValue(data));
|
||||
}
|
||||
chartPanel.repaint();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearNodeData() {
|
||||
if (isVisible()) {
|
||||
updateCharts();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCharts() {
|
||||
timeSeries.removeAllSeries();
|
||||
if (this.selectedNodes != null) {
|
||||
for(Node node: this.selectedNodes) {
|
||||
TimeSeries series = new TimeSeries(node.getName(), Second.class);
|
||||
// Reduce the number of items by grouping them and use the average for each group
|
||||
int groupSize = getGroupSize(node);
|
||||
if (groupSize > 1) {
|
||||
updateSeries(series, node, groupSize);
|
||||
} else {
|
||||
for (int i = 0, n = node.getSensorDataCount(); i < n; i++) {
|
||||
SensorData data = node.getSensorData(i);
|
||||
series.addOrUpdate(new Second(new Date(data.getNodeTime())), getSensorDataValue(data));
|
||||
}
|
||||
}
|
||||
timeSeries.addSeries(series);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected int getGroupSize(Node node) {
|
||||
if (maxItemCount > 0) {
|
||||
int sensorDataCount = node.getSensorDataCount();
|
||||
if (sensorDataCount > maxItemCount) {
|
||||
int groupSize = sensorDataCount / maxItemCount;
|
||||
if (sensorDataCount / groupSize >= maxItemCount) {
|
||||
groupSize++;
|
||||
}
|
||||
return groupSize;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
protected void updateSeries(TimeSeries series, Node node, int groupSize) {
|
||||
for (int i = 0, n = node.getSensorDataCount(); i < n; i += groupSize) {
|
||||
double value = 0.0;
|
||||
long time = 0L;
|
||||
for (int j = 0; j < groupSize; j++) {
|
||||
SensorData data = node.getSensorData(i);
|
||||
value += getSensorDataValue(data);
|
||||
time += data.getNodeTime() / 1000L;
|
||||
}
|
||||
series.addOrUpdate(new Second(new Date((time / groupSize) * 1000L)), value / groupSize);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getBaseShapeVisible() {
|
||||
return ((XYLineAndShapeRenderer)this.chart.getXYPlot().getRenderer()).getBaseShapesVisible();
|
||||
}
|
||||
|
||||
public void setBaseShapeVisible(boolean visible) {
|
||||
((XYLineAndShapeRenderer)this.chart.getXYPlot().getRenderer()).setBaseShapesVisible(visible);
|
||||
}
|
||||
|
||||
public int getRangeTick() {
|
||||
return rangeTick;
|
||||
}
|
||||
|
||||
public void setRangeTick(int rangeTick) {
|
||||
this.rangeTick = rangeTick;
|
||||
}
|
||||
|
||||
public double getRangeMinimumSize() {
|
||||
return chart.getXYPlot().getRangeAxis().getAutoRangeMinimumSize();
|
||||
}
|
||||
|
||||
public void setRangeMinimumSize(double size) {
|
||||
chart.getXYPlot().getRangeAxis().setAutoRangeMinimumSize(size);
|
||||
}
|
||||
|
||||
public boolean hasGlobalRange() {
|
||||
return hasGlobalRange;
|
||||
}
|
||||
|
||||
public void setGlobalRange(boolean hasGlobalRange) {
|
||||
if (this.hasGlobalRange != hasGlobalRange) {
|
||||
this.hasGlobalRange = hasGlobalRange;
|
||||
if (hasGlobalRange) {
|
||||
minValue = Double.MAX_VALUE;
|
||||
maxValue = Double.MIN_NORMAL;
|
||||
if (isVisible()) {
|
||||
updateGlobalRange();
|
||||
}
|
||||
} else {
|
||||
chart.getXYPlot().getRangeAxis().setAutoRange(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateGlobalRange() {
|
||||
if (hasGlobalRange) {
|
||||
if (minValue > maxValue) {
|
||||
for (int i = 0, n = server.getSensorDataCount(); i < n; i++) {
|
||||
double value = getSensorDataValue(server.getSensorData(i));
|
||||
if (value < minValue) minValue = value;
|
||||
if (value > maxValue) maxValue = value;
|
||||
}
|
||||
}
|
||||
if (minValue < maxValue) {
|
||||
double minSize = getRangeMinimumSize();
|
||||
double min = minValue;
|
||||
double max = maxValue;
|
||||
if (max - min < minSize) {
|
||||
double d = (minSize - (max - min)) / 2;
|
||||
min -= d;
|
||||
max += d;
|
||||
}
|
||||
if (rangeTick > 0) {
|
||||
min = ((int) (min - rangeTick / 2) / rangeTick) * rangeTick;
|
||||
// max = ((int) (max + rangeTick / 2) / rangeTick) * rangeTick;
|
||||
}
|
||||
chart.getXYPlot().getRangeAxis().setRange(min, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximal number of chart items to display for each node.
|
||||
*
|
||||
* @return the maximal number of chart items to display for each node or <code>0</code>
|
||||
* for unlimited number of chart items.
|
||||
*/
|
||||
public int getMaxItemCount() {
|
||||
return maxItemCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximal number of chart items to display for each node. Items will be
|
||||
* grouped and replaced by the average value when needed.
|
||||
*
|
||||
* @param maxItemCount - the maximal number of chart items to display for each node or
|
||||
* <code>0</code> for unlimited number (default)
|
||||
*/
|
||||
public void setMaxItemCount(int maxItemCount) {
|
||||
this.maxItemCount = maxItemCount;
|
||||
if (isVisible()) {
|
||||
updateCharts();
|
||||
}
|
||||
}
|
||||
|
||||
public void setVisible(boolean visible) {
|
||||
if (visible) {
|
||||
updateGlobalRange();
|
||||
updateCharts();
|
||||
} else {
|
||||
timeSeries.removeAllSeries();
|
||||
}
|
||||
super.setVisible(visible);
|
||||
}
|
||||
|
||||
protected abstract double getSensorDataValue(SensorData data);
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue