/*
* 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.
*
* -----------------------------------------------------------------
*
* CollectServer
*
* Authors : Joakim Eriksson, Niclas Finne
* Created : 3 jul 2008
*/
package org.contikios.contiki.collect;
import java.awt.BorderLayout;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.ListCellRenderer;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.contikios.contiki.collect.gui.AggregatedTimeChartPanel;
import org.contikios.contiki.collect.gui.BarChartPanel;
import org.contikios.contiki.collect.gui.MapPanel;
import org.contikios.contiki.collect.gui.NodeControl;
import org.contikios.contiki.collect.gui.NodeInfoPanel;
import org.contikios.contiki.collect.gui.SerialConsole;
import org.contikios.contiki.collect.gui.TimeChartPanel;
/**
*
*/
public class CollectServer implements SerialConnectionListener {
public static final String WINDOW_TITLE = "Sensor Data Collect with Contiki";
public static final String STDIN_COMMAND = "<STDIN>";
public static final String CONFIG_FILE = "collect.conf";
public static final String SENSORDATA_FILE = "sensordata.log";
public static final String CONFIG_DATA_FILE = "collect-data.conf";
public static final String INIT_SCRIPT = "collect-init.script";
public static final String FIRMWARE_FILE = "collect-view-shell.ihex";
/* Categories for the tab pane */
private static final String MAIN = "main";
private static final String NETWORK = "Network";
private static final String SENSORS = "Sensors";
private static final String POWER = "Power";
private Properties config = new Properties();
private String configFile;
private Properties configTable = new Properties();
private ArrayList<SensorData> sensorDataList = new ArrayList<SensorData>();
private PrintWriter sensorDataOutput;
private boolean isSensorLogUsed;
private Hashtable<String,Node> nodeTable = new Hashtable<String,Node>();
private Node[] nodeCache;
private JFrame window;
private JTabbedPane mainPanel;
private HashMap<String,JTabbedPane> categoryTable = new HashMap<String,JTabbedPane>();
private JMenuItem runInitScriptItem;
private final Visualizer[] visualizers;
private final MapPanel mapPanel;
private final SerialConsole serialConsole;
private final ConnectSerialAction connectSerialAction;
private final MoteProgramAction moteProgramAction;
private JFileChooser fileChooser;
private JList nodeList;
private DefaultListModel nodeModel;
private Node[] selectedNodes;
private SerialConnection serialConnection;
private boolean hasSerialOpened;
/* Do not auto send init script at startup */
private boolean doSendInitAtStartup = false;
private String initScript;
private boolean hasStarted = false;
private boolean doExitOnRequest = true;
private JMenuItem exitItem;
private int defaultMaxItemCount = 250;
private long nodeTimeDelta;
@SuppressWarnings("serial")
public CollectServer() {
loadConfig(config, CONFIG_FILE);
this.configFile = config.getProperty("config.datafile", CONFIG_DATA_FILE);
if (this.configFile != null) {
loadConfig(configTable, this.configFile);
}
this.initScript = config.getProperty("init.script", INIT_SCRIPT);
/* Make sure we have nice window decorations */
// JFrame.setDefaultLookAndFeelDecorated(true);
// JDialog.setDefaultLookAndFeelDecorated(true);
Rectangle maxSize = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getMaximumWindowBounds();
/* Create and set up the window */
window = new JFrame(WINDOW_TITLE + " (not connected)");
window.setLocationByPlatform(true);
if (maxSize != null) {
window.setMaximizedBounds(maxSize);
}
window.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
window.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
exit();
}
});
moteProgramAction = new MoteProgramAction("Program Nodes...");
connectSerialAction = new ConnectSerialAction("Connect to serial");
nodeModel = new DefaultListModel();
nodeModel.addElement("<All>");
nodeList = new JList(nodeModel);
nodeList.setPrototypeCellValue("888.888");
nodeList.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
if (!e.getValueIsAdjusting() && e.getSource() == nodeList) {
Node[] selected;
int iMin = nodeList.getMinSelectionIndex();
int iMax = nodeList.getMaxSelectionIndex();
if ((iMin < 0) || (iMax < 0)) {
selected = null;
} else if (nodeList.getSelectedIndex() == 0) {
selected = getNodes();
if (nodeModel.size() > 1) {
nodeList.setSelectionInterval(1, nodeModel.size() - 1);
}
} else {
Node[] tmp = new Node[1 + (iMax - iMin)];
int n = 0;
if (iMin < 1) {
iMin = 1;
}
for(int i = iMin; i <= iMax; i++) {
if (nodeList.isSelectedIndex(i)) {
tmp[n++] = (Node) nodeModel.getElementAt(i);
}
}
if (n != tmp.length) {
Node[] t = new Node[n];
System.arraycopy(tmp, 0, t, 0, n);
tmp = t;
}
selected = tmp;
}
selectNodes(selected, false);
}
}});
nodeList.setBorder(BorderFactory.createTitledBorder("Nodes"));
ListCellRenderer renderer = nodeList.getCellRenderer();
if (renderer instanceof JLabel) {
((JLabel)renderer).setHorizontalAlignment(JLabel.CENTER);
}
window.getContentPane().add(new JScrollPane(nodeList), BorderLayout.WEST);
mainPanel = new JTabbedPane();
mainPanel.setBackground(nodeList.getBackground());
mainPanel.setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT);
categoryTable.put(MAIN, mainPanel);
serialConsole = new SerialConsole(this, MAIN);
mapPanel = new MapPanel(this, "Sensor Map", MAIN, true);
String image = getConfig("collect.mapimage");
if (image != null) {
mapPanel.setMapBackground(image);
}
NodeControl nodeControl = new NodeControl(this, MAIN);
visualizers = new Visualizer[] {
nodeControl,
mapPanel,
new MapPanel(this, "Network Graph", MAIN, false),
new BarChartPanel(this, SENSORS, "Average Temperature", "Temperature", "Nodes", "Celsius",
new String[] { "Celsius" }) {
{
chart.getCategoryPlot().getRangeAxis().setStandardTickUnits(NumberAxis.createIntegerTickUnits());
}
protected void addSensorData(SensorData data) {
Node node = data.getNode();
String nodeName = node.getName();
SensorDataAggregator aggregator = node.getSensorDataAggregator();
dataset.addValue(aggregator.getAverageTemperature(), categories[0], nodeName);
}
},
new TimeChartPanel(this, SENSORS, "Temperature", "Temperature", "Time", "Celsius") {
{
chart.getXYPlot().getRangeAxis().setStandardTickUnits(NumberAxis.createIntegerTickUnits());
setRangeTick(5);
setRangeMinimumSize(10.0);
setGlobalRange(true);
}
protected double getSensorDataValue(SensorData data) {
return data.getTemperature();
}
},
new TimeChartPanel(this, SENSORS, "Battery Voltage", "Battery Voltage",
"Time", "Volt") {
{
setRangeTick(1);
setRangeMinimumSize(4.0);
setGlobalRange(true);
}
protected double getSensorDataValue(SensorData data) {
return data.getBatteryVoltage();
}
},
new TimeChartPanel(this, SENSORS, "Battery Indicator", "Battery Indicator",
"Time", "Indicator") {
{
chart.getXYPlot().getRangeAxis().setStandardTickUnits(NumberAxis.createIntegerTickUnits());
setRangeTick(5);
setRangeMinimumSize(10.0);
setGlobalRange(true);
}
protected double getSensorDataValue(SensorData data) {
return data.getBatteryIndicator();
}
},
new TimeChartPanel(this, SENSORS, "Relative Humidity", "Humidity", "Time", "%") {
{
chart.getXYPlot().getRangeAxis().setRange(0.0, 100.0);
}
protected double getSensorDataValue(SensorData data) {
return data.getHumidity();
}
},
new TimeChartPanel(this, SENSORS, "Light 1", "Light 1", "Time", "-") {
protected double getSensorDataValue(SensorData data) {
return data.getLight1();
}
},
new TimeChartPanel(this, SENSORS, "Light 2", "Light 2", "Time", "-") {
protected double getSensorDataValue(SensorData data) {
return data.getLight2();
}
},
new TimeChartPanel(this, NETWORK, "Neighbors", "Neighbor Count", "Time", "Neighbors") {
{
ValueAxis axis = chart.getXYPlot().getRangeAxis();
((NumberAxis)axis).setAutoRangeIncludesZero(true);
axis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
}
protected double getSensorDataValue(SensorData data) {
return data.getValue(SensorData.NUM_NEIGHBORS);
}
},
new TimeChartPanel(this, NETWORK, "Beacon Interval", "Beacon interval", "Time", "Interval (s)") {
{
ValueAxis axis = chart.getXYPlot().getRangeAxis();
((NumberAxis)axis).setAutoRangeIncludesZero(true);
axis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
}
protected double getSensorDataValue(SensorData data) {
return data.getValue(SensorData.BEACON_INTERVAL);
}
},
new TimeChartPanel(this, NETWORK, "Network Hops (Over Time)", "Network Hops", "Time", "Hops") {
{
ValueAxis axis = chart.getXYPlot().getRangeAxis();
((NumberAxis)axis).setAutoRangeIncludesZero(true);
axis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
}
protected double getSensorDataValue(SensorData data) {
return data.getValue(SensorData.HOPS);
}
},
new BarChartPanel(this, NETWORK, "Network Hops (Per Node)", "Network Hops", "Nodes", "Hops",
new String[] { "Last Hop", "Average Hops" }, false) {
{
chart.getCategoryPlot().getRangeAxis().setStandardTickUnits(NumberAxis.createIntegerTickUnits());
}
protected void addSensorData(SensorData data) {
dataset.addValue(data.getValue(SensorData.HOPS), categories[0], data.getNode().getName());
dataset.addValue(data.getNode().getSensorDataAggregator().getAverageValue(SensorData.HOPS), categories[1], data.getNode().getName());
}
},
new TimeChartPanel(this, NETWORK, "Routing Metric (Over Time)", "Routing Metric", "Time", "Routing Metric") {
{
ValueAxis axis = chart.getXYPlot().getRangeAxis();
((NumberAxis)axis).setAutoRangeIncludesZero(true);
axis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
}
protected double getSensorDataValue(SensorData data) {
return data.getValue(SensorData.RTMETRIC);
}
},
new AggregatedTimeChartPanel<boolean[]>(this, NETWORK, "Avg Routing Metric (Over Time)", "Time",
"Average Routing Metric") {
private int nodeCount;
{
ValueAxis axis = chart.getXYPlot().getRangeAxis();
((NumberAxis)axis).setAutoRangeIncludesZero(true);
axis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
}
@Override
protected boolean[] createState(Node node) {
return new boolean[1];
}
@Override
protected void clearState(Map<Node,boolean[]> map) {
nodeCount = 0;
for(boolean[] value : map.values()) {
value[0] = false;
}
}
@Override
protected String getTitle(int selectedCount, int dataCount, int duplicateCount) {
return "Average Routing Metric (" + dataCount + " packets from " + nodeCount + " node"
+ (nodeCount > 1 ? "s" : "") + ')';
}
@Override
protected int getTotalDataValue(int value) {
// Return average value
return nodeCount > 0 ? (value / nodeCount) : value;
}
@Override
protected int getSensorDataValue(SensorData data, boolean[] nodeState) {
if (!nodeState[0]) {
nodeCount++;
nodeState[0] = true;
}
return data.getValue(SensorData.RTMETRIC);
}
},
new TimeChartPanel(this, NETWORK, "ETX (Over Time)", "ETX to Next Hop", "Time", "ETX") {
{
ValueAxis axis = chart.getXYPlot().getRangeAxis();
((NumberAxis)axis).setAutoRangeIncludesZero(true);
axis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
}
protected double getSensorDataValue(SensorData data) {
return data.getBestNeighborETX();
}
},
new AggregatedTimeChartPanel<int[]>(this, NETWORK,
"Next Hop (Over Time)", "Time", "Next Hop Changes") {
{
ValueAxis axis = chart.getXYPlot().getRangeAxis();
((NumberAxis)axis).setAutoRangeIncludesZero(true);
axis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
}
@Override
protected int[] createState(Node node) {
return new int[1];
}
@Override
protected void clearState(Map<Node,int[]> map) {
for(int[] value : map.values()) {
value[0] = 0;
}
}
@Override
protected int getSensorDataValue(SensorData sd, int[] nodeState) {
boolean hasBest = nodeState[0] != 0;
int bestNeighbor = sd.getValue(SensorData.BEST_NEIGHBOR);
if (bestNeighbor != 0 && bestNeighbor != nodeState[0]) {
nodeState[0] = bestNeighbor;
return hasBest ? 1 : 0;
}
return 0;
}
},
new TimeChartPanel(this, NETWORK, "Latency", "Latency", "Time", "Seconds") {
protected double getSensorDataValue(SensorData data) {
return data.getLatency();
}
},
new AggregatedTimeChartPanel<Node>(this, NETWORK,
"Received (Over Time)", "Time", "Received Packets") {
{
ValueAxis axis = chart.getXYPlot().getRangeAxis();
((NumberAxis)axis).setAutoRangeIncludesZero(true);
axis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
}
@Override
protected String getTitle(int nodeCount, int dataCount, int duplicateCount) {
return "Received " + dataCount + " packets from " + nodeCount + " node"
+ (nodeCount > 1 ? "s" : "")
+ (duplicateCount > 0 ? (" (" + duplicateCount + " duplicates)") : "");
}
@Override
protected Node createState(Node node) {
return node;
}
@Override
protected int getSensorDataValue(SensorData sd, Node node) {
return 1;
}
},
new AggregatedTimeChartPanel<int[]>(this, NETWORK,
"Lost (Over Time)", "Time", "Estimated Lost Packets") {
private int totalLost;
{
ValueAxis axis = chart.getXYPlot().getRangeAxis();
((NumberAxis)axis).setAutoRangeIncludesZero(true);
axis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
}
@Override
protected String getTitle(int nodeCount, int dataCount, int duplicateCount) {
return "Received " + dataCount + " packets from " + nodeCount
+ " node" + (nodeCount > 1 ? "s" : "") + ". Estimated "
+ totalLost + " lost packet" + (totalLost == 1 ? "" : "s")
+ '.';
}
@Override
protected int[] createState(Node node) {
return new int[1];
}
@Override
protected void clearState(Map<Node,int[]> map) {
totalLost = 0;
for(int[] v : map.values()) {
v[0] = 0;
}
}
@Override
protected int getSensorDataValue(SensorData sd, int[] nodeState) {
int lastSeqno = nodeState[0];
int seqno = sd.getSeqno();
nodeState[0] = seqno;
if (seqno > lastSeqno + 1 && lastSeqno != 0) {
int estimatedLost = seqno - lastSeqno - 1;
totalLost += estimatedLost;
return estimatedLost;
}
return 0;
}
},
new BarChartPanel(this, NETWORK, "Received (Per Node)", "Received Packets Per Node", "Nodes", "Packets",
new String[] { "Packets", "Duplicates" }) {
{
chart.getCategoryPlot().getRangeAxis().setStandardTickUnits(NumberAxis.createIntegerTickUnits());
}
protected void addSensorData(SensorData data) {
Node node = data.getNode();
SensorDataAggregator sda = node.getSensorDataAggregator();
dataset.addValue(sda.getDataCount(), categories[0], node.getName());
dataset.addValue(sda.getDuplicateCount(), categories[1], node.getName());
}
},
new BarChartPanel(this, NETWORK, "Received (5 min)", "Received Packets (last 5 min)", "Nodes", "Packets",
new String[] { "Packets", "Duplicates" }) {
{
chart.getCategoryPlot().getRangeAxis().setStandardTickUnits(NumberAxis.createIntegerTickUnits());
}
protected void addSensorData(SensorData data) {
Node node = data.getNode();
int packetCount = 0;
int duplicateCount = 0;
long earliestData = System.currentTimeMillis() - (5 * 60 * 1000);
for(int index = node.getSensorDataCount() - 1; index >= 0; index--) {
SensorData sd = node.getSensorData(index);
if (sd.getNodeTime() < earliestData) {
break;
}
if (sd.isDuplicate()) {
duplicateCount++;
} else {
packetCount++;
}
}
dataset.addValue(packetCount, categories[0], node.getName());
dataset.addValue(duplicateCount, categories[1], node.getName());
}
},
new BarChartPanel(this, POWER, "Average Power", "Average Power Consumption",
"Nodes", "Power (mW)",
new String[] { "LPM", "CPU", "Radio listen", "Radio transmit" }) {
{
ValueAxis axis = chart.getCategoryPlot().getRangeAxis();
((NumberAxis)axis).setAutoRangeIncludesZero(true);
}
protected void addSensorData(SensorData data) {
Node node = data.getNode();
String nodeName = node.getName();
SensorDataAggregator aggregator = node.getSensorDataAggregator();
dataset.addValue(aggregator.getLPMPower(), categories[0], nodeName);
dataset.addValue(aggregator.getCPUPower(), categories[1], nodeName);
dataset.addValue(aggregator.getListenPower(), categories[2], nodeName);
dataset.addValue(aggregator.getTransmitPower(), categories[3], nodeName);
}
},
new BarChartPanel(this, POWER, "Radio Duty Cycle", "Average Radio Duty Cycle",
"Nodes", "Duty Cycle (%)",
new String[] { "Radio listen", "Radio transmit" }) {
{
ValueAxis axis = chart.getCategoryPlot().getRangeAxis();
((NumberAxis)axis).setAutoRangeIncludesZero(true);
}
protected void addSensorData(SensorData data) {
Node node = data.getNode();
String nodeName = node.getName();
SensorDataAggregator aggregator = node.getSensorDataAggregator();
dataset.addValue(100 * aggregator.getAverageDutyCycle(SensorInfo.TIME_LISTEN),
categories[0], nodeName);
dataset.addValue(100 * aggregator.getAverageDutyCycle(SensorInfo.TIME_TRANSMIT),
categories[1], nodeName);
}
},
new BarChartPanel(this, POWER, "Instantaneous Power",
"Instantaneous Power Consumption", "Nodes", "Power (mW)",
new String[] { "LPM", "CPU", "Radio listen", "Radio transmit" }) {
{
ValueAxis axis = chart.getCategoryPlot().getRangeAxis();
((NumberAxis)axis).setAutoRangeIncludesZero(true);
}
protected void addSensorData(SensorData data) {
Node node = data.getNode();
String nodeName = node.getName();
dataset.addValue(data.getLPMPower(), categories[0], nodeName);
dataset.addValue(data.getCPUPower(), categories[1], nodeName);
dataset.addValue(data.getListenPower(), categories[2], nodeName);
dataset.addValue(data.getTransmitPower(), categories[3], nodeName);
}
},
new TimeChartPanel(this, POWER, "Power History", "Historical Power Consumption", "Time", "mW") {
protected double getSensorDataValue(SensorData data) {
return data.getAveragePower();
}
},
new NodeInfoPanel(this, MAIN),
serialConsole
};
for (int i = 0, n = visualizers.length; i < n; i++) {
String category = visualizers[i].getCategory();
JTabbedPane pane = categoryTable.get(category);
if (pane == null) {
pane = new JTabbedPane();
pane.setBackground(nodeList.getBackground());
pane.setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT);
categoryTable.put(category, pane);
mainPanel.add(category, pane);
}
pane.add(visualizers[i].getTitle(), visualizers[i].getPanel());
}
JTabbedPane pane = categoryTable.get(nodeControl.getCategory());
if (pane != null) {
pane.setSelectedComponent(nodeControl.getPanel());
}
window.getContentPane().add(mainPanel, BorderLayout.CENTER);
// Setup menu
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
fileMenu.setMnemonic(KeyEvent.VK_F);
menuBar.add(fileMenu);
fileMenu.add(new JMenuItem(connectSerialAction));
fileMenu.add(new JMenuItem(moteProgramAction));
fileMenu.addSeparator();
final JMenuItem clearMapItem = new JMenuItem("Remove Map Background");
clearMapItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
mapPanel.setMapBackground(null);
clearMapItem.setEnabled(false);
configTable.remove("collect.mapimage");
}
});
clearMapItem.setEnabled(mapPanel.getMapBackground() != null);
JMenuItem item = new JMenuItem("Select Map Background...");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (fileChooser == null) {
fileChooser = new JFileChooser();
}
int reply = fileChooser.showOpenDialog(window);
if (reply == JFileChooser.APPROVE_OPTION) {
File file = fileChooser.getSelectedFile();
String name = file.getAbsolutePath();
if (!mapPanel.setMapBackground(file.getAbsolutePath())) {
JOptionPane.showMessageDialog(window, "Failed to set background image", "Error", JOptionPane.ERROR_MESSAGE);
} else {
configTable.put("collect.mapimage", name);
save();
}
clearMapItem.setEnabled(mapPanel.getMapBackground() != null);
}
}
});
fileMenu.add(item);
fileMenu.add(clearMapItem);
item = new JMenuItem("Save Settings");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
save();
JOptionPane.showMessageDialog(window, "Settings have been saved.");
}
});
fileMenu.add(item);
fileMenu.addSeparator();
item = new JMenuItem("Clear Sensor Data...");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int reply = isSensorLogUsed
? JOptionPane.showConfirmDialog(window, "Also clear the sensor data log file?")
: JOptionPane.NO_OPTION;
if (reply == JOptionPane.YES_OPTION) {
// Clear data from both memory and sensor log file
clearSensorDataLog();
clearSensorData();
} else if (reply == JOptionPane.NO_OPTION) {
// Only clear data from memory
clearSensorData();
}
}
});
fileMenu.add(item);
fileMenu.addSeparator();
exitItem = new JMenuItem("Exit", KeyEvent.VK_X);
exitItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
exit();
}
});
fileMenu.add(exitItem);
JMenu toolsMenu = new JMenu("Tools");
toolsMenu.setMnemonic(KeyEvent.VK_T);
menuBar.add(toolsMenu);
runInitScriptItem = new JMenuItem("Run Init Script");
runInitScriptItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
mainPanel.setSelectedComponent(serialConsole.getPanel());
if (serialConnection != null && serialConnection.isOpen()) {
runInitScript();
} else {
JOptionPane.showMessageDialog(mainPanel, "No serial port connection", "No connected node", JOptionPane.ERROR_MESSAGE);
}
}
});
runInitScriptItem.setEnabled(false);
toolsMenu.add(runInitScriptItem);
toolsMenu.addSeparator();
item = new JMenuItem("Set Max Item Count...");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int value = getUserInputAsInteger("Specify Max Item Count",
"Please specify max item count for the time charts.\n" +
"Charts with more values will aggregate the values into fewer items.",
defaultMaxItemCount);
if (value > 0) {
defaultMaxItemCount = value;
if (visualizers != null) {
for(Visualizer v : visualizers) {
if (v instanceof TimeChartPanel) {
((TimeChartPanel)v).setMaxItemCount(defaultMaxItemCount);
}
}
}
}
}
});
toolsMenu.add(item);
final JCheckBoxMenuItem baseShapeItem = new JCheckBoxMenuItem("Base Shape Visible");
baseShapeItem.setSelected(true);
baseShapeItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
boolean visible = baseShapeItem.getState();
if (visualizers != null) {
for(Visualizer v : visualizers) {
if (v instanceof TimeChartPanel) {
((TimeChartPanel)v).setBaseShapeVisible(visible);
}
}
}
}
});
toolsMenu.add(baseShapeItem);
window.setJMenuBar(menuBar);
window.pack();
String bounds = configTable.getProperty("collect.bounds");
if (bounds != null) {
String[] b = bounds.split(",");
if (b.length == 4) {
window.setBounds(Integer.parseInt(b[0]), Integer.parseInt(b[1]),
Integer.parseInt(b[2]), Integer.parseInt(b[3]));
}
}
for(Object key: configTable.keySet()) {
String property = key.toString();
if (!property.startsWith("collect")) {
getNode(property, true);
}
}
}
private int getUserInputAsInteger(String title, String message, int defaultValue) {
String s = (String)JOptionPane.showInputDialog(
window, message, title, JOptionPane.PLAIN_MESSAGE, null, null, Integer.toString(defaultValue));
if (s != null) {
try {
return Integer.parseInt(s);
} catch (Exception e) {
JOptionPane.showMessageDialog(window, "Illegal value", "Error", JOptionPane.ERROR_MESSAGE);
}
}
return -1;
}
public void start(SerialConnection connection) {
if (hasStarted) {
throw new IllegalStateException("already started");
}
hasStarted = true;
this.serialConnection = connection;
if (isSensorLogUsed) {
initSensorData();
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
window.setVisible(true);
}
});
connectToSerial();
}
protected void connectToSerial() {
if (serialConnection != null && !serialConnection.isOpen()) {
String comPort = serialConnection.getComPort();
if (comPort == null && serialConnection.isMultiplePortsSupported()) {
comPort = MoteFinder.selectComPort(window);
}
if (comPort != null || !serialConnection.isMultiplePortsSupported()) {
serialConnection.open(comPort);
}
}
}
public void stop() {
save();
if (serialConnection != null) {
serialConnection.close();
}
PrintWriter output = this.sensorDataOutput;
if (output != null) {
output.close();
}
window.setVisible(false);
}
public void setUseSensorDataLog(boolean useSensorLog) {
this.isSensorLogUsed = useSensorLog;
}
public void setExitOnRequest(boolean doExit) {
this.doExitOnRequest = doExit;
if (exitItem != null) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
exitItem.setEnabled(doExitOnRequest);
}
});
}
}
private void exit() {
if (doExitOnRequest) {
stop();
System.exit(0);
} else {
Toolkit.getDefaultToolkit().beep();
}
}
private void sleep(long delay) {
try {
Thread.sleep(delay);
} catch (InterruptedException e1) {
// Ignore
}
}
protected boolean hasInitScript() {
return initScript != null && new File(initScript).canRead();
}
protected void runInitScript() {
if (initScript != null) {
runScript(initScript);
}
}
protected void runScript(final String scriptFileName) {
new Thread("scripter") {
public void run() {
try {
BufferedReader in = new BufferedReader(new FileReader(scriptFileName));
String line;
while ((line = in.readLine()) != null) {
if (line.length() == 0 || line.charAt(0) == '#') {
// Ignore empty lines and comments
} else if (line.startsWith("echo ")) {
line = line.substring(5).trim();
if (line.indexOf('%') >= 0) {
line = line.replace("%TIME%", "" + (System.currentTimeMillis() / 1000));
}
sendToNode(line);
} else if (line.startsWith("sleep ")) {
long delay = Integer.parseInt(line.substring(6).trim());
Thread.sleep(delay * 1000);
} else {
System.err.println("Unknown script command: " + line);
break;
}
}
in.close();
} catch (Exception e) {
System.err.println("Failed to run script: " + scriptFileName);
e.printStackTrace();
}
}
}.start();
}
public String getConfig(String property) {
return getConfig(property, null);
}
public String getConfig(String property, String defaultValue) {
return configTable.getProperty(property, config.getProperty(property, defaultValue));
}
public void setConfig(String property, String value) {
configTable.setProperty(property, value);
}
public void removeConfig(String property) {
configTable.remove(property);
}
public int getDefaultMaxItemCount() {
return defaultMaxItemCount;
}
public Action getMoteProgramAction() {
return moteProgramAction;
}
public Action getConnectSerialAction() {
return connectSerialAction;
}
protected void setSystemMessage(final String message) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
boolean isOpen = serialConnection != null && serialConnection.isOpen();
if (message == null) {
window.setTitle(WINDOW_TITLE);
} else {
window.setTitle(WINDOW_TITLE + " (" + message + ')');
}
connectSerialAction.putValue(ConnectSerialAction.NAME,
isOpen ? "Disconnect from serial" : "Connect to serial");
runInitScriptItem.setEnabled(isOpen
&& serialConnection.isSerialOutputSupported() && hasInitScript());
}
});
}
// -------------------------------------------------------------------
// Node Handling
// -------------------------------------------------------------------
public synchronized Node[] getNodes() {
if (nodeCache == null) {
Node[] tmp = nodeTable.values().toArray(new Node[nodeTable.size()]);
Arrays.sort(tmp);
nodeCache = tmp;
}
return nodeCache;
}
public Node addNode(String nodeID) {
return getNode(nodeID, true);
}
private Node getNode(final String nodeID, boolean notify) {
Node node = nodeTable.get(nodeID);
if (node == null) {
node = new Node(nodeID);
nodeTable.put(nodeID, node);
synchronized (this) {
nodeCache = null;
}
if (notify) {
final Node newNode = node;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
boolean added = false;
for (int i = 1, n = nodeModel.size(); i < n; i++) {
int cmp = newNode.compareTo((Node) nodeModel.get(i));
if (cmp < 0) {
nodeModel.insertElementAt(newNode, i);
added = true;
break;
} else if (cmp == 0) {
// node already added
added = true;
break;
}
}
if (!added) {
nodeModel.addElement(newNode);
}
if (visualizers != null) {
for (int i = 0, n = visualizers.length; i < n; i++) {
visualizers[i].nodeAdded(newNode);
}
}
}
});
}
}
return node;
}
public void selectNodes(Node[] nodes) {
selectNodes(nodes, true);
}
private void selectNodes(Node[] nodes, boolean updateList) {
if (nodes != selectedNodes) {
selectedNodes = nodes;
if (updateList) {
nodeList.clearSelection();
if (selectedNodes != null) {
for (int i = 0, n = selectedNodes.length; i < n; i++) {
int index = nodeModel.indexOf(selectedNodes[i]);
if (index >= 0) {
nodeList.addSelectionInterval(index, index);
}
}
}
}
if (visualizers != null) {
for (int i = 0, n = visualizers.length; i < n; i++) {
visualizers[i].nodesSelected(nodes);
}
}
}
}
public Node[] getSelectedNodes() {
return selectedNodes;
}
// -------------------------------------------------------------------
// Node location handling
// -------------------------------------------------------------------
private boolean loadConfig(Properties properties, String configFile) {
try {
BufferedInputStream input =
new BufferedInputStream(new FileInputStream(configFile));
try {
properties.load(input);
} finally {
input.close();
}
return true;
} catch (FileNotFoundException e) {
// No configuration file exists.
} catch (IOException e) {
System.err.println("Failed to read configuration file: " + configFile);
e.printStackTrace();
}
return false;
}
private void save() {
if (configFile != null) {
configTable.setProperty("collect.bounds", "" + window.getX() + ',' + window.getY() + ',' + window.getWidth() + ',' + window.getHeight());
if (visualizers != null) {
for(Visualizer v : visualizers) {
if (v instanceof Configurable) {
((Configurable)v).updateConfig(configTable);
}
}
}
saveConfig(configTable, configFile);
}
}
private void saveConfig(Properties properties, String configFile) {
try {
File fp = new File(configFile);
if (fp.exists()) {
File targetFp = new File(configFile + ".bak");
if (targetFp.exists()) {
targetFp.delete();
}
fp.renameTo(targetFp);
}
FileOutputStream output = new FileOutputStream(configFile);
try {
properties.store(output, "Configuration for Collect");
} finally {
output.close();
}
} catch (IOException e) {
System.err.println("failed to save configuration to " + configFile);
e.printStackTrace();
}
}
// -------------------------------------------------------------------
// Serial communication
// -------------------------------------------------------------------
public boolean sendToNode(String data) {
if (serialConnection != null && serialConnection.isOpen() && serialConnection.isSerialOutputSupported()) {
serialConsole.addSerialData("SEND: " + data);
serialConnection.writeSerialData(data);
return true;
}
return false;
}
public void handleIncomingData(long systemTime, String line) {
if (line.length() == 0 || line.charAt(0) == '#') {
// Ignore empty lines, comments, and annotations.
return;
}
SensorData sensorData = SensorData.parseSensorData(this, line, systemTime);
if (sensorData != null) {
// Sensor data received
handleSensorData(sensorData);
return;
}
System.out.println("SERIAL: " + line);
serialConsole.addSerialData(line);
}
// -------------------------------------------------------------------
// Node time estimation
// -------------------------------------------------------------------
public long getNodeTime() {
return System.currentTimeMillis() + nodeTimeDelta;
}
private void updateNodeTime(SensorData sensorData) {
this.nodeTimeDelta = sensorData.getNodeTime() - System.currentTimeMillis();
}
// -------------------------------------------------------------------
// SensorData handling
// -------------------------------------------------------------------
public int getSensorDataCount() {
return sensorDataList.size();
}
public SensorData getSensorData(int i) {
return sensorDataList.get(i);
}
private void handleSensorData(final SensorData sensorData) {
System.out.println("SENSOR DATA: " + sensorData);
saveSensorData(sensorData);
if (sensorData.getNode().addSensorData(sensorData)) {
updateNodeTime(sensorData);
sensorDataList.add(sensorData);
handleLinks(sensorData);
if (visualizers != null) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
for (int i = 0, n = visualizers.length; i < n; i++) {
visualizers[i].nodeDataReceived(sensorData);
}
}
});
}
}
}
private void handleLinks(SensorData sensorData) {
String nodeID = sensorData.getBestNeighborID();
if (nodeID != null) {
Node neighbor = addNode(nodeID);
Node source = sensorData.getNode();
Link link = source.getLink(neighbor);
link.setETX(sensorData.getBestNeighborETX());
link.setLastActive(sensorData.getNodeTime());
}
}
private void initSensorData() {
loadSensorData(SENSORDATA_FILE, true);
}
private boolean loadSensorData(String filename, boolean isStrict) {
File fp = new File(filename);
if (fp.exists() && fp.canRead()) {
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader(fp));
String line;
int no = 0;
while ((line = in.readLine()) != null) {
no++;
if (line.length() == 0 || line.charAt(0) == '#') {
// Ignore empty lines and comments
} else {
SensorData data = SensorData.parseSensorData(this, line);
if (data != null) {
if (data.getNode().addSensorData(data)) {
updateNodeTime(data);
sensorDataList.add(data);
handleLinks(data);
}
} else if (isStrict) {
// TODO exit here?
System.err.println("Failed to parse sensor data from log line " + no + ": " + line);
}
}
}
in.close();
} catch (IOException e) {
System.err.println("Failed to read sensor data log from " + fp.getAbsolutePath());
e.printStackTrace();
return false;
}
}
return true;
}
private void saveSensorData(SensorData data) {
PrintWriter output = this.sensorDataOutput;
if (output == null && isSensorLogUsed) {
try {
output = sensorDataOutput = new PrintWriter(new FileWriter(SENSORDATA_FILE, true));
} catch (IOException e) {
System.err.println("Failed to add sensor data to log '" + SENSORDATA_FILE + '\'');
e.printStackTrace();
}
}
if (output != null) {
output.println(data.toString());
output.flush();
}
}
private void clearSensorData() {
sensorDataList.clear();
Node[] nodes = getNodes();
this.selectedNodes = null;
nodeList.clearSelection();
if (nodeModel.size() > 1) {
nodeModel.removeRange(1, nodeModel.size() - 1);
}
this.nodeTable.clear();
synchronized (this) {
this.nodeCache = null;
}
if (nodes != null) {
for(Node node : nodes) {
node.removeAllSensorData();
}
}
if (visualizers != null) {
for(Visualizer v : visualizers) {
v.nodesSelected(null);
v.clearNodeData();
}
}
// Remove any saved node positions
for(String key: configTable.keySet().toArray(new String[0])) {
String property = key.toString();
if (!property.startsWith("collect")) {
configTable.remove(property);
}
}
}
private void clearSensorDataLog() {
PrintWriter output = this.sensorDataOutput;
if (output != null) {
output.close();
}
// Remove the sensor data log
new File(SENSORDATA_FILE).delete();
this.sensorDataOutput = null;
}
protected class ConnectSerialAction extends AbstractAction implements Runnable {
private static final long serialVersionUID = 1L;
private boolean isRunning;
public ConnectSerialAction(String name) {
super(name);
}
public void actionPerformed(ActionEvent e) {
if (!isRunning) {
isRunning = true;
new Thread(this, "serial").start();
}
}
public void run() {
try {
if (serialConnection != null) {
if (serialConnection.isOpen()) {
serialConnection.close();
} else {
connectToSerial();
}
} else {
JOptionPane.showMessageDialog(window, "No serial connection configured", "Error", JOptionPane.ERROR_MESSAGE);
}
} finally {
isRunning = false;
}
}
}
protected class MoteProgramAction extends AbstractAction implements Runnable {
private static final long serialVersionUID = 1L;
private boolean isRunning = false;
public MoteProgramAction(String name) {
super(name);
}
public void actionPerformed(ActionEvent e) {
if (!isRunning) {
isRunning = true;
new Thread(this, "program thread").start();
}
}
@Override
public void run() {
try {
MoteProgrammer mp = new MoteProgrammer();
mp.setParentComponent(window);
mp.setFirmwareFile(FIRMWARE_FILE);
mp.searchForMotes();
String[] motes = mp.getMotes();
if (motes == null || motes.length == 0) {
JOptionPane.showMessageDialog(window, "Could not find any connected nodes", "Error", JOptionPane.ERROR_MESSAGE);
return;
}
int reply = JOptionPane.showConfirmDialog(window, "Found " + motes.length + " connected nodes.\n"
+ "Do you want to upload the firmware " + FIRMWARE_FILE + '?');
if (reply == JFileChooser.APPROVE_OPTION) {
boolean wasOpen = serialConnection != null && serialConnection.isOpen();
if (serialConnection != null) {
serialConnection.close();
}
if (wasOpen) {
Thread.sleep(1000);
}
mp.programMotes();
mp.waitForProcess();
if (wasOpen) {
connectToSerial();
}
}
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(window, "Programming failed: " + e, "Error", JOptionPane.ERROR_MESSAGE);
} finally {
isRunning = false;
}
}
}
// -------------------------------------------------------------------
// SerialConnection Listener
// -------------------------------------------------------------------
@Override
public void serialData(SerialConnection connection, String line) {
handleIncomingData(System.currentTimeMillis(), line);
}
@Override
public void serialOpened(SerialConnection connection) {
String connectionName = connection.getConnectionName();
serialConsole.addSerialData("*** Serial console listening on " + connectionName + " ***");
hasSerialOpened = true;
if (connection.isMultiplePortsSupported()) {
String comPort = connection.getComPort();
// Remember the last selected serial port
configTable.put("collect.serialport", comPort);
}
setSystemMessage("connected to " + connectionName);
if (!connection.isSerialOutputSupported()) {
serialConsole.addSerialData("*** Serial output not supported ***");
} else if (doSendInitAtStartup) {
// Send any initial commands
doSendInitAtStartup = false;
if (hasInitScript()) {
// Wait a short time before running the init script
sleep(3000);
runInitScript();
}
}
}
@Override
public void serialClosed(SerialConnection connection) {
String prefix;
if (hasSerialOpened) {
serialConsole.addSerialData("*** Serial connection terminated ***");
prefix = "Serial connection terminated.\n";
hasSerialOpened = false;
setSystemMessage("not connected");
} else {
prefix = "Failed to connect to " + connection.getConnectionName() + '\n';
}
if (!connection.isClosed()) {
if (connection.isMultiplePortsSupported()) {
String options[] = {"Retry", "Search for connected nodes", "Cancel"};
int value = JOptionPane.showOptionDialog(window,
prefix + "Do you want to retry or search for connected nodes?",
"Reconnect to serial port?",
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE,
null, options, options[0]);
if (value == JOptionPane.CLOSED_OPTION || value == 2) {
// exit();
} else {
String comPort = connection.getComPort();
if (value == 1) {
// Select new serial port
comPort = MoteFinder.selectComPort(window);
if (comPort == null) {
// exit();
}
}
// Try to open com port again
if (comPort != null) {
connection.open(comPort);
}
}
} else {
// JOptionPane.showMessageDialog(window,
// prefix, "Serial Connection Closed", JOptionPane.ERROR_MESSAGE);
}
}
}
// -------------------------------------------------------------------
// Main
// -------------------------------------------------------------------
public static void main(String[] args) {
boolean resetSensorLog = false;
boolean useSensorLog = true;
boolean useSerialOutput = true;
String host = null;
String command = null;
String logFileToLoad = null;
String comPort = null;
int port = -1;
for(int i = 0, n = args.length; i < n; i++) {
String arg = args[i];
if (arg.length() == 2 && arg.charAt(0) == '-') {
switch (arg.charAt(1)) {
case 'a':
if (i + 1 < n) {
host = args[++i];
int pIndex = host.indexOf(':');
if (pIndex > 0) {
port = Integer.parseInt(host.substring(pIndex + 1));
host = host.substring(0, pIndex);
}
} else {
usage(arg);
}
break;
case 'c':
if (i + 1 < n) {
command = args[++i];
} else {
usage(arg);
}
break;
case 'p':
if (i + 1 < n) {
port = Integer.parseInt(args[++i]);
} else {
usage(arg);
}
break;
case 'r':
resetSensorLog = true;
break;
case 'n':
useSensorLog = false;
break;
case 'i':
useSerialOutput = false;
break;
case 'f':
command = STDIN_COMMAND;
if (i + 1 < n && !args[i + 1].startsWith("-")) {
logFileToLoad = args[++i];
}
break;
case 'h':
usage(null);
break;
default:
usage(arg);
break;
}
} else if (comPort == null) {
comPort = arg;
} else {
usage(arg);
}
}
CollectServer server = new CollectServer();
SerialConnection serialConnection;
if (host != null) {
if (port <= 0) {
port = 60001;
}
serialConnection = new TCPClientConnection(server, host, port);
} else if (port > 0) {
serialConnection = new UDPConnection(server, port);
} else if (command == null) {
serialConnection = new SerialDumpConnection(server);
} else if (command == STDIN_COMMAND) {
serialConnection = new StdinConnection(server);
} else {
serialConnection = new CommandConnection(server, command);
}
if (comPort == null) {
comPort = server.getConfig("collect.serialport");
}
if (comPort != null) {
serialConnection.setComPort(comPort);
}
if (!useSerialOutput) {
serialConnection.setSerialOutputSupported(false);
}
server.isSensorLogUsed = useSensorLog;
if (useSensorLog && resetSensorLog) {
server.clearSensorDataLog();
}
if (logFileToLoad != null) {
server.loadSensorData(logFileToLoad, false);
}
server.start(serialConnection);
}
private static void usage(String arg) {
if (arg != null) {
System.err.println("Unknown argument '" + arg + '\'');
}
System.err.println("Usage: java CollectServer [-n] [-i] [-r] [-f [file]] [-a host:port] [-p port] [-c command] [COMPORT]");
System.err.println(" -n : Do not read or save sensor data log");
System.err.println(" -r : Clear any existing sensor data log at startup");
System.err.println(" -i : Do not allow serial output");
System.err.println(" -f : Read serial data from standard in");
System.err.println(" -a : Connect to specified host:port");
System.err.println(" -p : Read data from specified UDP port");
System.err.println(" -c : Use specified command for serial data input/output");
System.err.println(" COMPORT: The serial port to connect to");
System.exit(arg != null ? 1 : 0);
}
}