osd-contiki/tools/cooja/java/org/contikios/cooja/plugins/TimeLine.java
Fredrik Osterlind b5c94910ac Renamed Java packages to match our contiki-os.org domain. Note that this commit does not affect external dependencies
like /tools/mspsim.

This is a very simple modification that affects a very large number of files in Contiki: Cooja,
/platform/cooja, Collect-view, Coffe-manager, and Cooja simulation files (.csc).

I've gone through Contiki to update all references I could find. Nevertheless, this commit will likely
break external dependencies, like saved Cooja simulation files.
2013-11-20 16:43:27 +01:00

2481 lines
82 KiB
Java

/*
* Copyright (c) 2009, 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.
*
*/
package org.contikios.cooja.plugins;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Observable;
import java.util.Observer;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JToolTip;
import javax.swing.KeyStroke;
import javax.swing.Popup;
import javax.swing.PopupFactory;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.apache.log4j.Logger;
import org.jdom.Element;
import org.contikios.cooja.ClassDescription;
import org.contikios.cooja.Cooja;
import org.contikios.cooja.HasQuickHelp;
import org.contikios.cooja.Mote;
import org.contikios.cooja.Plugin;
import org.contikios.cooja.PluginType;
import org.contikios.cooja.SimEventCentral.LogOutputEvent;
import org.contikios.cooja.SimEventCentral.LogOutputListener;
import org.contikios.cooja.Simulation;
import org.contikios.cooja.VisPlugin;
import org.contikios.cooja.Watchpoint;
import org.contikios.cooja.WatchpointMote;
import org.contikios.cooja.WatchpointMote.WatchpointListener;
import org.contikios.cooja.interfaces.LED;
import org.contikios.cooja.interfaces.Radio;
import org.contikios.cooja.interfaces.Radio.RadioEvent;
import org.contikios.cooja.motes.AbstractEmulatedMote;
/**
* Shows events such as mote logs, LEDs, and radio transmissions, in a timeline.
*
* @author Fredrik Osterlind
*/
@ClassDescription("Timeline")
@PluginType(PluginType.SIM_STANDARD_PLUGIN)
public class TimeLine extends VisPlugin implements HasQuickHelp {
private static final long serialVersionUID = -883154261246961973L;
public static final int LED_PIXEL_HEIGHT = 2;
public static final int EVENT_PIXEL_HEIGHT = 4;
public static final int TIME_MARKER_PIXEL_HEIGHT = 6;
public static final int FIRST_MOTE_PIXEL_OFFSET = TIME_MARKER_PIXEL_HEIGHT + EVENT_PIXEL_HEIGHT;
private static final Color COLOR_BACKGROUND = Color.WHITE;
private static final boolean PAINT_ZERO_WIDTH_EVENTS = true;
private static final int TIMELINE_UPDATE_INTERVAL = 100;
private double currentPixelDivisor = 200;
private static final long[] ZOOM_LEVELS = {
1, 2, 5, 10,
20, 50, 100, 200, 500, 1000,
2000, 5000, 10000, 20000, 50000, 100000 };
private boolean needZoomOut = false;
private static Logger logger = Logger.getLogger(TimeLine.class);
private int paintedMoteHeight = EVENT_PIXEL_HEIGHT;
private Simulation simulation;
private LogOutputListener newMotesListener;
/* Expermental features: Use currently active plugin to filter Timeline Log outputs */
private LogListener logEventFilterPlugin = null;
private JScrollPane timelineScrollPane;
private MoteRuler timelineMoteRuler;
private JComponent timeline;
private Observer moteHighlightObserver = null;
private ArrayList<Mote> highlightedMotes = new ArrayList<Mote>();
private final static Color HIGHLIGHT_COLOR = Color.CYAN;
private ArrayList<MoteObservation> activeMoteObservers = new ArrayList<MoteObservation>();
private ArrayList<MoteEvents> allMoteEvents = new ArrayList<MoteEvents>();
private boolean showRadioRXTX = true;
private boolean showRadioChannels = false;
private boolean showRadioOnoff = true;
private boolean showLeds = true;
private boolean showLogOutputs = false;
private boolean showWatchpoints = false;
private Point popupLocation = null;
private JCheckBox showWatchpointsCheckBox;
private JCheckBox showLogsCheckBox;
private JCheckBox showLedsCheckBox;
private JCheckBox showRadioOnoffCheckbox;
private JCheckBox showRadioChannelsCheckbox;
private JCheckBox showRadioTXRXCheckbox;
/**
* @param simulation Simulation
* @param gui GUI
*/
public TimeLine(final Simulation simulation, final Cooja gui) {
super("Timeline", gui);
this.simulation = simulation;
currentPixelDivisor = ZOOM_LEVELS[ZOOM_LEVELS.length/2];
/* Menus */
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
JMenu editMenu = new JMenu("Edit");
JMenu motesMenu = new JMenu("Motes");
JMenu eventsMenu = new JMenu("Events");
JMenu viewMenu = new JMenu("View");
JMenu zoomMenu = new JMenu("Zoom");
menuBar.add(fileMenu);
menuBar.add(editMenu);
menuBar.add(viewMenu);
menuBar.add(zoomMenu);
menuBar.add(eventsMenu);
menuBar.add(motesMenu);
this.setJMenuBar(menuBar);
motesMenu.add(new JMenuItem(addMoteAction));
zoomMenu.add(new JMenuItem(zoomInAction));
zoomMenu.add(new JMenuItem(zoomOutAction));
zoomMenu.add(new JMenuItem(zoomSliderAction));
viewMenu.add(new JCheckBoxMenuItem(executionDetailsAction) {
private static final long serialVersionUID = 8314556794750277113L;
public boolean isSelected() {
return executionDetails;
}
});
fileMenu.add(new JMenuItem(saveDataAction));
fileMenu.add(new JMenuItem(statisticsAction));
editMenu.add(new JMenuItem(clearAction));
showRadioTXRXCheckbox = createEventCheckbox("Radio traffic", "Show radio transmissions, receptions, and collisions");
showRadioTXRXCheckbox.setName("showRadioRXTX");
showRadioTXRXCheckbox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
showRadioRXTX = ((JCheckBox) e.getSource()).isSelected();
recalculateMoteHeight();
}
});
eventsMenu.add(showRadioTXRXCheckbox);
showRadioOnoffCheckbox = createEventCheckbox("Radio on/off", "Show radio hardware state");
showRadioOnoffCheckbox.setSelected(showRadioOnoff);
showRadioOnoffCheckbox.setName("showRadioHW");
showRadioOnoffCheckbox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
showRadioOnoff = ((JCheckBox) e.getSource()).isSelected();
recalculateMoteHeight();
}
});
eventsMenu.add(showRadioOnoffCheckbox);
showRadioChannelsCheckbox = createEventCheckbox("Radio channel", "Show different radio channels");
showRadioChannelsCheckbox.setSelected(showRadioChannels);
showRadioChannelsCheckbox.setName("showRadioChannels");
showRadioChannelsCheckbox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
showRadioChannels = ((JCheckBox) e.getSource()).isSelected();
recalculateMoteHeight();
}
});
eventsMenu.add(showRadioChannelsCheckbox);
showLedsCheckBox = createEventCheckbox("LEDs", "Show LED state");
showLedsCheckBox.setSelected(showLeds);
showLedsCheckBox.setName("showLEDs");
showLedsCheckBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
showLeds = ((JCheckBox) e.getSource()).isSelected();
recalculateMoteHeight();
}
});
eventsMenu.add(showLedsCheckBox);
showLogsCheckBox = createEventCheckbox("Log output", "Show mote log output, such as printf()'s");
showLogsCheckBox.setSelected(showLogOutputs);
showLogsCheckBox.setName("showLogOutput");
showLogsCheckBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
showLogOutputs = ((JCheckBox) e.getSource()).isSelected();
/* Check whether there is an active log listener that is used to filter logs */
logEventFilterPlugin = (LogListener) simulation.getCooja().getPlugin(
LogListener.class.getName());
if (showLogOutputs) {
if (logEventFilterPlugin != null) {
logger.info("Filtering shown log outputs by use of " + Cooja.getDescriptionOf(LogListener.class) + " plugin");
} else {
logger.info("No active " + Cooja.getDescriptionOf(LogListener.class) + " plugin, not filtering log outputs");
}
}
recalculateMoteHeight();
}
});
eventsMenu.add(showLogsCheckBox);
showWatchpointsCheckBox = createEventCheckbox("Watchpoints", "Show code watchpoints (for emulated motes)");
showWatchpointsCheckBox.setSelected(showWatchpoints);
showWatchpointsCheckBox.setName("showWatchpoints");
showWatchpointsCheckBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
showWatchpoints = ((JCheckBox) e.getSource()).isSelected();
recalculateMoteHeight();
}
});
eventsMenu.add(showWatchpointsCheckBox);
/* Box: events to observe */
/* Panel: timeline canvas w. scroll pane and add mote button */
timeline = new Timeline();
timelineScrollPane = new JScrollPane(
timeline,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
timelineScrollPane.getHorizontalScrollBar().setUnitIncrement(50);
timelineMoteRuler = new MoteRuler();
timelineScrollPane.setRowHeaderView(timelineMoteRuler);
timelineScrollPane.setBackground(Color.WHITE);
/* Zoom in/out via keyboard*/
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, KeyEvent.CTRL_DOWN_MASK), "zoomIn");
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, KeyEvent.SHIFT_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK), "zoomIn");
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, KeyEvent.CTRL_DOWN_MASK), "zoomIn");
getActionMap().put("zoomIn", zoomInAction);
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, KeyEvent.CTRL_DOWN_MASK), "zoomOut");
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, KeyEvent.CTRL_DOWN_MASK), "zoomOut");
getActionMap().put("zoomOut", zoomOutAction);
/* getContentPane().add(splitPane);*/
getContentPane().add(timelineScrollPane);
recalculateMoteHeight();
pack();
numberMotesWasUpdated();
/* Automatically add/delete motes.
* This listener also observes mote log outputs. */
simulation.getEventCentral().addLogOutputListener(newMotesListener = new LogOutputListener() {
public void moteWasAdded(Mote mote) {
addMote(mote);
}
public void moteWasRemoved(Mote mote) {
removeMote(mote);
}
public void removedLogOutput(LogOutputEvent ev) {
}
public void newLogOutput(LogOutputEvent ev) {
/* Log output */
Mote mote = ev.getMote();
LogEvent logEvent = new LogEvent(ev);
/* TODO Optimize */
for (MoteEvents moteEvents: allMoteEvents) {
if (moteEvents.mote == mote) {
moteEvents.addLog(logEvent);
break;
}
}
}
});
for (Mote m: simulation.getMotes()) {
addMote(m);
}
/* Update timeline for the duration of the plugin */
repaintTimelineTimer.start();
gui.addMoteHighlightObserver(moteHighlightObserver = new Observer() {
public void update(Observable obs, Object obj) {
if (!(obj instanceof Mote)) {
return;
}
final Timer timer = new Timer(100, null);
final Mote mote = (Mote) obj;
timer.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
/* Count down */
if (timer.getDelay() < 90) {
timer.stop();
highlightedMotes.remove(mote);
repaint();
return;
}
/* Toggle highlight state */
if (highlightedMotes.contains(mote)) {
highlightedMotes.remove(mote);
} else {
highlightedMotes.add(mote);
}
timer.setDelay(timer.getDelay()-1);
repaint();
}
});
timer.start();
}
});
/* XXX HACK: here we set the position and size of the window when it appears on a blank simulation screen. */
this.setLocation(0, gui.getDesktopPane().getHeight() - 166);
this.setSize(gui.getDesktopPane().getWidth(), 166);
}
public void startPlugin() {
super.startPlugin();
showWatchpointsCheckBox.setSelected(showWatchpoints);
showLogsCheckBox.setSelected(showLogOutputs);
showLedsCheckBox.setSelected(showLeds);
showRadioOnoffCheckbox.setSelected(showRadioOnoff);
showRadioChannelsCheckbox.setSelected(showRadioChannels);
showRadioTXRXCheckbox.setSelected(showRadioRXTX);
}
private JCheckBox createEventCheckbox(String text, String tooltip) {
JCheckBox checkBox = new JCheckBox(text, true);
checkBox.setToolTipText(tooltip);
return checkBox;
}
private Action removeMoteAction = new AbstractAction() {
private static final long serialVersionUID = 2924285037480429045L;
public void actionPerformed(ActionEvent e) {
JComponent b = (JComponent) e.getSource();
Mote m = (Mote) b.getClientProperty("mote");
removeMote(m);
}
};
private Action removeAllOtherMotesAction = new AbstractAction() {
private static final long serialVersionUID = 2924285037480429045L;
public void actionPerformed(ActionEvent e) {
JComponent b = (JComponent) e.getSource();
Mote m = (Mote) b.getClientProperty("mote");
MoteEvents[] mes = allMoteEvents.toArray(new MoteEvents[0]);
for (MoteEvents me: mes) {
if (me.mote == m) {
continue;
}
removeMote(me.mote);
}
}
};
private Action sortMoteAction = new AbstractAction() {
private static final long serialVersionUID = 621116674700872058L;
public void actionPerformed(ActionEvent e) {
JComponent b = (JComponent) e.getSource();
Mote m = (Mote) b.getClientProperty("mote");
/* Sort by distance */
ArrayList<MoteEvents> sortedMoteEvents = new ArrayList<MoteEvents>();
for (MoteEvents me: allMoteEvents.toArray(new MoteEvents[0])) {
double d = me.mote.getInterfaces().getPosition().getDistanceTo(m);
int i=0;
for (i=0; i < sortedMoteEvents.size(); i++) {
double d2 = m.getInterfaces().getPosition().getDistanceTo(sortedMoteEvents.get(i).mote);
if (d < d2) {
break;
}
}
sortedMoteEvents.add(i, me);
}
allMoteEvents = sortedMoteEvents;
numberMotesWasUpdated();
}
};
private Action topMoteAction = new AbstractAction() {
private static final long serialVersionUID = 4683178751482241843L;
public void actionPerformed(ActionEvent e) {
JComponent b = (JComponent) e.getSource();
Mote m = (Mote) b.getClientProperty("mote");
/* Sort by distance */
MoteEvents mEvent = null;
for (MoteEvents me: allMoteEvents.toArray(new MoteEvents[0])) {
if (me.mote == m) {
mEvent = me;
break;
}
}
allMoteEvents.remove(mEvent);
allMoteEvents.add(0, mEvent);
numberMotesWasUpdated();
}
};
private Action addMoteAction = new AbstractAction("Show motes...") {
private static final long serialVersionUID = 7546685285707302865L;
public void actionPerformed(ActionEvent e) {
JComboBox<Object> source = new JComboBox<Object>();
source.addItem("All motes");
for (Mote m: simulation.getMotes()) {
source.addItem(m);
}
Object description[] = {
source
};
JOptionPane optionPane = new JOptionPane();
optionPane.setMessage(description);
optionPane.setMessageType(JOptionPane.QUESTION_MESSAGE);
String options[] = new String[] {"Cancel", "Show"};
optionPane.setOptions(options);
optionPane.setInitialValue(options[1]);
JDialog dialog = optionPane.createDialog(Cooja.getTopParentContainer(), "Show mote in timeline");
dialog.setVisible(true);
if (optionPane.getValue() == null || !optionPane.getValue().equals("Show")) {
return;
}
if (source.getSelectedItem().equals("All motes")) {
for (Mote m: simulation.getMotes()) {
addMote(m);
}
} else {
addMote((Mote) source.getSelectedItem());
}
}
};
private void forceRepaintAndFocus(final long focusTime, final double focusCenter) {
forceRepaintAndFocus(focusTime, focusCenter, true);
}
private void forceRepaintAndFocus(final long focusTime, final double focusCenter, final boolean mark) {
lastRepaintSimulationTime = -1; /* Force repaint */
repaintTimelineTimer.getActionListeners()[0].actionPerformed(null); /* Force size update*/
SwingUtilities.invokeLater(new Runnable() {
public void run() {
int w = timeline.getVisibleRect().width;
/* centerPixel-leftPixel <=> focusCenter*w; */
int centerPixel = (int) (focusTime/currentPixelDivisor);
int leftPixel = (int) (focusTime/currentPixelDivisor - focusCenter*w);
Rectangle r = new Rectangle(
leftPixel, 0,
w, 1
);
timeline.scrollRectToVisible(r);
/* Time ruler */
if (mark) {
mousePixelPositionX = centerPixel;
mouseDownPixelPositionX = centerPixel;
mousePixelPositionY = timeline.getHeight();
}
}
});
}
private int zoomGetLevel (final double zoomDivisor) {
int zoomLevel = 0;
while (zoomLevel < ZOOM_LEVELS.length) {
if (zoomDivisor <= ZOOM_LEVELS[zoomLevel]) break;
zoomLevel++;
}
return zoomLevel;
}
private int zoomGetLevel () {
return zoomGetLevel(currentPixelDivisor);
}
private double zoomLevelToDivisor (int zoomLevel) {
if (0 > zoomLevel) {
zoomLevel = 0;
} else if (ZOOM_LEVELS.length <= zoomLevel) {
zoomLevel = ZOOM_LEVELS.length - 1;
}
return ZOOM_LEVELS[zoomLevel];
}
private void zoomFinish (final double zoomDivisor,
final long focusTime,
final double focusCenter) {
currentPixelDivisor = zoomDivisor;
String note = "";
if (ZOOM_LEVELS[0] >= zoomDivisor) {
currentPixelDivisor = ZOOM_LEVELS[0];
note = " (MIN)";
} else if (ZOOM_LEVELS[ZOOM_LEVELS.length-1] <= zoomDivisor) {
currentPixelDivisor = ZOOM_LEVELS[ZOOM_LEVELS.length-1];
note = " (MAX)";
}
if (zoomDivisor != currentPixelDivisor) {
logger.info("Zoom level: adjusted out-of-range " + zoomDivisor + " us/pixel");
}
logger.info("Zoom level: " + currentPixelDivisor + " microseconds/pixel " + note);
forceRepaintAndFocus(focusTime, focusCenter);
}
private void zoomFinishLevel (final int zoomLevel,
final long focusTime,
final double focusCenter) {
final double cpd = zoomLevelToDivisor(zoomLevel);
zoomFinish(cpd, focusTime, focusCenter);
}
private void zoomIn (final long focusTime,
final double focusCenter) {
zoomFinishLevel(zoomGetLevel()-1, focusTime, focusCenter);
}
private void zoomOut (final long focusTime,
final double focusCenter) {
zoomFinishLevel(zoomGetLevel()+1, focusTime, focusCenter);
}
private Action zoomInAction = new AbstractAction("Zoom in (Ctrl+)") {
private static final long serialVersionUID = -2592452356547803615L;
public void actionPerformed(ActionEvent e) {
Rectangle r = timeline.getVisibleRect();
int pixelX = r.x + r.width/2;
if (popupLocation != null) {
pixelX = popupLocation.x;
popupLocation = null;
}
if (mousePixelPositionX > 0) {
pixelX = mousePixelPositionX;
}
final long centerTime = (long) (pixelX*currentPixelDivisor);
zoomIn(centerTime, 0.5);
}
};
private Action zoomOutAction = new AbstractAction("Zoom out (Ctrl-)") {
private static final long serialVersionUID = 6837091379835151725L;
public void actionPerformed(ActionEvent e) {
Rectangle r = timeline.getVisibleRect();
int pixelX = r.x + r.width/2;
if (popupLocation != null) {
pixelX = popupLocation.x;
popupLocation = null;
}
if (mousePixelPositionX > 0) {
pixelX = mousePixelPositionX;
}
final long centerTime = (long) (pixelX*currentPixelDivisor);
zoomOut(centerTime, 0.5);
}
};
private Action zoomSliderAction = new AbstractAction("Zoom slider (Ctrl+Mouse)") {
private static final long serialVersionUID = -4288046377707363837L;
public void actionPerformed(ActionEvent e) {
final int zoomLevel = zoomGetLevel();
final JSlider zoomSlider = new JSlider(JSlider.VERTICAL, 0, ZOOM_LEVELS.length-1, zoomLevel);
zoomSlider.setInverted(true);
zoomSlider.setPaintTicks(true);
zoomSlider.setPaintLabels(false);
final long centerTime = (long) (popupLocation.x*currentPixelDivisor);
zoomSlider.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
final int zoomLevel = zoomSlider.getValue();
zoomFinishLevel(zoomLevel, centerTime, 0.5);
}
});
final JPopupMenu zoomPopup = new JPopupMenu();
zoomPopup.add(zoomSlider);
zoomPopup.show(TimeLine.this, TimeLine.this.getWidth()/2, 0);
zoomSlider.requestFocus();
}
};
/**
* Save logged raw data to file for post-processing.
*/
private Action saveDataAction = new AbstractAction("Save to file...") {
private static final long serialVersionUID = 975176793514425718L;
public void actionPerformed(ActionEvent e) {
JFileChooser fc = new JFileChooser();
int returnVal = fc.showSaveDialog(Cooja.getTopParentContainer());
if (returnVal != JFileChooser.APPROVE_OPTION) {
return;
}
File saveFile = fc.getSelectedFile();
if (saveFile.exists()) {
String s1 = "Overwrite";
String s2 = "Cancel";
Object[] options = { s1, s2 };
int n = JOptionPane.showOptionDialog(
Cooja.getTopParentContainer(),
"A file with the same name already exists.\nDo you want to remove it?",
"Overwrite existing file?", JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE, null, options, s1);
if (n != JOptionPane.YES_OPTION) {
return;
}
}
if (saveFile.exists() && !saveFile.canWrite()) {
logger.fatal("No write access to file");
return;
}
try {
BufferedWriter outStream = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(
saveFile)));
/* Output all events (sorted per mote) */
for (MoteEvents moteEvents: allMoteEvents) {
for (MoteEvent ev: moteEvents.ledEvents) {
outStream.write(moteEvents.mote + "\t" + ev.time + "\t" + ev.toString() + "\n");
}
for (MoteEvent ev: moteEvents.logEvents) {
outStream.write(moteEvents.mote + "\t" + ev.time + "\t" + ev.toString() + "\n");
}
for (MoteEvent ev: moteEvents.radioChannelEvents) {
outStream.write(moteEvents.mote + "\t" + ev.time + "\t" + ev.toString() + "\n");
}
for (MoteEvent ev: moteEvents.radioHWEvents) {
outStream.write(moteEvents.mote + "\t" + ev.time + "\t" + ev.toString() + "\n");
}
for (MoteEvent ev: moteEvents.radioRXTXEvents) {
outStream.write(moteEvents.mote + "\t" + ev.time + "\t" + ev.toString() + "\n");
}
for (MoteEvent ev: moteEvents.watchpointEvents) {
outStream.write(moteEvents.mote + "\t" + ev.time + "\t" + ev.toString() + "\n");
}
}
outStream.close();
} catch (Exception ex) {
logger.fatal("Could not write to file: " + saveFile);
return;
}
}
};
private Action clearAction = new AbstractAction("Clear all timeline data") {
private static final long serialVersionUID = -4592530582786872403L;
public void actionPerformed(ActionEvent e) {
if (simulation.isRunning()) {
simulation.invokeSimulationThread(new Runnable() {
public void run() {
clear();
}
});
} else {
clear();
}
}
};
public void clear() {
for (MoteEvents me : allMoteEvents) {
me.clear();
}
repaint();
}
private class MoteStatistics {
Mote mote;
long onTimeRedLED = 0, onTimeGreenLED = 0, onTimeBlueLED = 0;
int nrLogs = 0;
long radioOn = 0;
long onTimeRX = 0, onTimeTX = 0, onTimeInterfered = 0;
public String toString() {
return toString(true, true, true, true);
}
public String toString(boolean logs, boolean leds, boolean radioHW, boolean radioRXTX) {
long duration = simulation.getSimulationTime(); /* XXX */
StringBuilder sb = new StringBuilder();
String moteDesc = (mote!=null?"" + mote.getID():"AVERAGE") + " ";
if (logs) {
sb.append(moteDesc + "nr_logs " + nrLogs + "\n");
}
if (leds) {
sb.append(moteDesc + "led_red " + onTimeRedLED + " us " + 100.0*((double)onTimeRedLED/duration) + " %\n");
sb.append(moteDesc + "led_green " + onTimeGreenLED + " us " + 100.0*((double)onTimeGreenLED/duration) + " %\n");
sb.append(moteDesc + "led_blue " + onTimeBlueLED + " us " + 100.0*((double)onTimeBlueLED/duration) + " %\n");
}
if (radioHW) {
sb.append(moteDesc + "radio_on " + radioOn + " us " + 100.0*((double)radioOn/duration) + " %\n");
}
if (radioRXTX) {
sb.append(moteDesc + "radio_tx " + onTimeTX + " us " + 100.0*((double)onTimeTX/duration) + " %\n");
sb.append(moteDesc + "radio_rx " + onTimeRX + " us " + 100.0*((double)onTimeRX/duration) + " %\n");
sb.append(moteDesc + "radio_int " + onTimeInterfered + " us " + 100.0*((double)onTimeInterfered/duration) + " %\n");
}
return sb.toString();
}
}
private Action statisticsAction = new AbstractAction("Print statistics to console") {
private static final long serialVersionUID = 8671605486913497397L;
public void actionPerformed(ActionEvent e) {
if (simulation.isRunning()) {
simulation.stopSimulation();
}
logger.info(extractStatistics());
}
};
public String extractStatistics() {
return extractStatistics(true, true, true, true);
}
public synchronized String extractStatistics(
boolean logs, boolean leds, boolean radioHW, boolean radioRXTX) {
StringBuilder output = new StringBuilder();
/* Process all events (per mote basis) */
ArrayList<MoteStatistics> allStats = new ArrayList<MoteStatistics>();
for (MoteEvents moteEvents: allMoteEvents) {
MoteStatistics stats = new MoteStatistics();
allStats.add(stats);
stats.mote = moteEvents.mote;
if (leds) {
for (MoteEvent ev: moteEvents.ledEvents) {
if (!(ev instanceof LEDEvent)) continue;
LEDEvent ledEvent = (LEDEvent) ev;
/* Red */
if (ledEvent.red) {
/* LED is on, add time interval */
if (ledEvent.next == null) {
stats.onTimeRedLED += (simulation.getSimulationTime() - ledEvent.time);
} else {
stats.onTimeRedLED += (ledEvent.next.time - ledEvent.time);
}
}
/* Green */
if (ledEvent.green) {
/* LED is on, add time interval */
if (ledEvent.next == null) {
stats.onTimeGreenLED += (simulation.getSimulationTime() - ledEvent.time);
} else {
stats.onTimeGreenLED += (ledEvent.next.time - ledEvent.time);
}
}
/* Blue */
if (ledEvent.blue) {
/* LED is on, add time interval */
if (ledEvent.next == null) {
stats.onTimeBlueLED += (simulation.getSimulationTime() - ledEvent.time);
} else {
stats.onTimeBlueLED += (ledEvent.next.time - ledEvent.time);
}
}
}
}
if (logs) {
for (MoteEvent ev: moteEvents.logEvents) {
if (!(ev instanceof LogEvent)) continue;
stats.nrLogs++;
}
}
if (radioHW) {
for (MoteEvent ev: moteEvents.radioHWEvents) {
if (!(ev instanceof RadioHWEvent)) continue;
RadioHWEvent hwEvent = (RadioHWEvent) ev;
if (hwEvent.on) {
/* HW is on */
if (hwEvent.next == null) {
stats.radioOn += (simulation.getSimulationTime() - hwEvent.time);
} else {
stats.radioOn += (hwEvent.next.time - hwEvent.time);
}
}
}
}
if (radioRXTX) {
for (MoteEvent ev: moteEvents.radioRXTXEvents) {
if (!(ev instanceof RadioRXTXEvent)) continue;
RadioRXTXEvent rxtxEvent = (RadioRXTXEvent) ev;
if (rxtxEvent.state == RXTXRadioEvent.IDLE) {
continue;
}
long diff;
if (rxtxEvent.next == null) {
diff = (simulation.getSimulationTime() - rxtxEvent.time);
} else {
diff = (rxtxEvent.next.time - rxtxEvent.time);
}
if (rxtxEvent.state == RXTXRadioEvent.TRANSMITTING) {
stats.onTimeTX += diff;
continue;
}
if (rxtxEvent.state == RXTXRadioEvent.INTERFERED) {
stats.onTimeInterfered += diff;
continue;
}
if (rxtxEvent.state == RXTXRadioEvent.RECEIVING) {
stats.onTimeRX += diff;
continue;
}
}
}
output.append(stats.toString(logs, leds, radioHW, radioRXTX));
}
if (allStats.size() == 0) {
return output.toString();
}
/* Average */
MoteStatistics average = new MoteStatistics();
for (MoteStatistics stats: allStats) {
average.onTimeRedLED += stats.onTimeRedLED;
average.onTimeGreenLED += stats.onTimeGreenLED;
average.onTimeBlueLED += stats.onTimeBlueLED;
average.radioOn += stats.radioOn;
average.onTimeRX += stats.onTimeRX;
average.onTimeTX += stats.onTimeTX;
average.onTimeInterfered += stats.onTimeInterfered;
}
average.onTimeBlueLED /= allStats.size();
average.onTimeGreenLED /= allStats.size();
average.onTimeBlueLED /= allStats.size();
average.radioOn /= allStats.size();
average.onTimeRX /= allStats.size();
average.onTimeTX /= allStats.size();
average.onTimeInterfered /= allStats.size();
output.append(average.toString(logs, leds, radioHW, radioRXTX));
return output.toString();
}
public void trySelectTime(final long toTime) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
/* Mark selected time in time ruler */
final int toPixel = (int) (toTime / currentPixelDivisor);
mousePixelPositionX = toPixel;
mouseDownPixelPositionX = toPixel;
mousePixelPositionY = timeline.getHeight();
/* Check if time is already visible */
Rectangle vis = timeline.getVisibleRect();
if (toPixel >= vis.x && toPixel < vis.x + vis.width) {
repaint();
return;
}
forceRepaintAndFocus(toTime, 0.5, false);
}
});
}
private Action radioLoggerAction = new AbstractAction("Show in " + Cooja.getDescriptionOf(RadioLogger.class)) {
private static final long serialVersionUID = 7690116136861949864L;
public void actionPerformed(ActionEvent e) {
if (popupLocation == null) {
return;
}
long time = (long) (popupLocation.x*currentPixelDivisor);
Plugin[] plugins = simulation.getCooja().getStartedPlugins();
for (Plugin p: plugins) {
if (!(p instanceof RadioLogger)) {
continue;
}
/* Select simulation time */
RadioLogger plugin = (RadioLogger) p;
plugin.trySelectTime(time);
}
}
};
private Action logListenerAction = new AbstractAction("Show in " + Cooja.getDescriptionOf(LogListener.class)) {
private static final long serialVersionUID = -8626118368774023257L;
public void actionPerformed(ActionEvent e) {
if (popupLocation == null) {
return;
}
long time = (long) (popupLocation.x*currentPixelDivisor);
Plugin[] plugins = simulation.getCooja().getStartedPlugins();
for (Plugin p: plugins) {
if (!(p instanceof LogListener)) {
continue;
}
/* Select simulation time */
LogListener plugin = (LogListener) p;
plugin.trySelectTime(time);
}
}
};
private Action showInAllAction = new AbstractAction("Show in " + Cooja.getDescriptionOf(LogListener.class) + " and " + Cooja.getDescriptionOf(RadioLogger.class)) {
private static final long serialVersionUID = -2458733078524773995L;
public void actionPerformed(ActionEvent e) {
logListenerAction.actionPerformed(null);
radioLoggerAction.actionPerformed(null);
}
};
private boolean executionDetails = false;
private Action executionDetailsAction = new AbstractAction("Show execution details in tooltips") {
private static final long serialVersionUID = -8626118368774023257L;
public void actionPerformed(ActionEvent e) {
executionDetails = !executionDetails;
}
};
private void numberMotesWasUpdated() {
/* Plugin title */
if (allMoteEvents.isEmpty()) {
setTitle("Timeline");
} else {
setTitle("Timeline showing " + allMoteEvents.size() + " motes");
}
timelineMoteRuler.revalidate();
timelineMoteRuler.repaint();
timeline.revalidate();
timeline.repaint();
}
/* XXX Keeps track of observed mote interfaces */
class MoteObservation {
private Observer observer;
private Observable observable;
private Mote mote;
private WatchpointMote watchpointMote; /* XXX */
private WatchpointListener watchpointListener; /* XXX */
public MoteObservation(Mote mote, Observable observable, Observer observer) {
this.mote = mote;
this.observable = observable;
this.observer = observer;
}
/* XXX Special case, should be generalized */
public MoteObservation(Mote mote, WatchpointMote watchpointMote, WatchpointListener listener) {
this.mote = mote;
this.watchpointMote = watchpointMote;
this.watchpointListener = listener;
}
public Mote getMote() {
return mote;
}
/**
* Disconnect observer from observable (stop observing) and clean up resources (remove pointers).
*/
public void dispose() {
if (observable != null) {
observable.deleteObserver(observer);
mote = null;
observable = null;
observer = null;
}
/* XXX */
if (watchpointMote != null) {
watchpointMote.removeWatchpointListener(watchpointListener);
watchpointMote = null;
watchpointListener = null;
}
}
}
private void addMoteObservers(final Mote mote, final MoteEvents moteEvents) {
/* LEDs */
final LED moteLEDs = mote.getInterfaces().getLED();
if (moteLEDs != null) {
LEDEvent startupEv = new LEDEvent(
simulation.getSimulationTime(),
moteLEDs.isRedOn(),
moteLEDs.isGreenOn(),
moteLEDs.isYellowOn()
);
moteEvents.addLED(startupEv);
Observer observer = new Observer() {
public void update(Observable o, Object arg) {
LEDEvent ev = new LEDEvent(
simulation.getSimulationTime(),
moteLEDs.isRedOn(),
moteLEDs.isGreenOn(),
moteLEDs.isYellowOn()
);
moteEvents.addLED(ev);
}
};
moteLEDs.addObserver(observer);
activeMoteObservers.add(new MoteObservation(mote, moteLEDs, observer));
}
/* Radio OnOff, RXTX, and channels */
final Radio moteRadio = mote.getInterfaces().getRadio();
if (moteRadio != null) {
RadioChannelEvent startupChannel = new RadioChannelEvent(
simulation.getSimulationTime(), moteRadio.getChannel(), moteRadio.isRadioOn());
moteEvents.addRadioChannel(startupChannel);
RadioHWEvent startupHW = new RadioHWEvent(
simulation.getSimulationTime(), moteRadio.isRadioOn());
moteEvents.addRadioHW(startupHW);
RadioRXTXEvent startupRXTX = new RadioRXTXEvent(
simulation.getSimulationTime(), RXTXRadioEvent.IDLE);
moteEvents.addRadioRXTX(startupRXTX);
Observer observer = new Observer() {
int lastChannel = -1;
public void update(Observable o, Object arg) {
RadioEvent radioEv = moteRadio.getLastEvent();
String details = null;
if (executionDetails && mote instanceof AbstractEmulatedMote) {
details = ((AbstractEmulatedMote) mote).getExecutionDetails();
if (details != null) {
details = "<br>" + details.replace("\n", "<br>");
}
}
/* Radio channel */
int nowChannel = moteRadio.getChannel();
if (nowChannel != lastChannel) {
lastChannel = nowChannel;
RadioChannelEvent ev = new RadioChannelEvent(
simulation.getSimulationTime(), nowChannel, moteRadio.isRadioOn());
moteEvents.addRadioChannel(ev);
ev.details = details;
}
if (radioEv == RadioEvent.HW_ON ||
radioEv == RadioEvent.HW_OFF) {
RadioHWEvent ev = new RadioHWEvent(
simulation.getSimulationTime(), moteRadio.isRadioOn());
moteEvents.addRadioHW(ev);
ev.details = details;
/* Also create another channel event here */
lastChannel = nowChannel;
RadioChannelEvent ev2 = new RadioChannelEvent(
simulation.getSimulationTime(), nowChannel, moteRadio.isRadioOn());
ev2.details = details;
moteEvents.addRadioChannel(ev2);
}
/* Radio RXTX events */
if (radioEv == RadioEvent.TRANSMISSION_STARTED ||
radioEv == RadioEvent.TRANSMISSION_FINISHED ||
radioEv == RadioEvent.RECEPTION_STARTED ||
radioEv == RadioEvent.RECEPTION_INTERFERED ||
radioEv == RadioEvent.RECEPTION_FINISHED) {
RadioRXTXEvent ev;
/* Override events, instead show state */
if (moteRadio.isTransmitting()) {
ev = new RadioRXTXEvent(
simulation.getSimulationTime(), RXTXRadioEvent.TRANSMITTING);
} else if (!moteRadio.isRadioOn()) {
ev = new RadioRXTXEvent(
simulation.getSimulationTime(), RXTXRadioEvent.IDLE);
} else if (moteRadio.isInterfered()) {
ev = new RadioRXTXEvent(
simulation.getSimulationTime(), RXTXRadioEvent.INTERFERED);
} else if (moteRadio.isReceiving()) {
ev = new RadioRXTXEvent(
simulation.getSimulationTime(), RXTXRadioEvent.RECEIVING);
} else {
ev = new RadioRXTXEvent(
simulation.getSimulationTime(), RXTXRadioEvent.IDLE);
}
moteEvents.addRadioRXTX(ev);
ev.details = details;
}
}
};
moteRadio.addObserver(observer);
activeMoteObservers.add(new MoteObservation(mote, moteRadio, observer));
}
/* Watchpoints */
if (mote instanceof WatchpointMote) {
final WatchpointMote watchpointMote = ((WatchpointMote)mote);
WatchpointListener listener = new WatchpointListener() {
public void watchpointTriggered(Watchpoint watchpoint) {
WatchpointEvent ev = new WatchpointEvent(simulation.getSimulationTime(), watchpoint);
if (executionDetails && mote instanceof AbstractEmulatedMote) {
String details = ((AbstractEmulatedMote) mote).getExecutionDetails();
if (details != null) {
details = "<br>" + details.replace("\n", "<br>");
ev.details = details;
}
}
moteEvents.addWatchpoint(ev);
}
public void watchpointsChanged() {
}
};
watchpointMote.addWatchpointListener(listener);
activeMoteObservers.add(new MoteObservation(mote, watchpointMote, listener));
}
}
private void addMote(Mote newMote) {
if (newMote == null) {
return;
}
for (MoteEvents moteEvents: allMoteEvents) {
if (moteEvents.mote == newMote) {
return;
}
}
MoteEvents newMoteLog = new MoteEvents(newMote);
allMoteEvents.add(newMoteLog);
addMoteObservers(newMote, newMoteLog);
numberMotesWasUpdated();
}
private void removeMote(Mote mote) {
MoteEvents remove = null;
for (MoteEvents moteEvents: allMoteEvents) {
if (moteEvents.mote == mote) {
remove = moteEvents;
break;
}
}
if (remove == null) {
logger.warn("No such observed mote: " + mote);
return;
}
allMoteEvents.remove(remove);
/* Remove mote observers */
MoteObservation[] moteObservers = activeMoteObservers.toArray(new MoteObservation[0]);
for (MoteObservation o: moteObservers) {
if (o.getMote() == mote) {
o.dispose();
activeMoteObservers.remove(o);
}
}
numberMotesWasUpdated();
}
private void recalculateMoteHeight() {
int h = EVENT_PIXEL_HEIGHT;
if (showRadioRXTX) {
h += EVENT_PIXEL_HEIGHT;
}
if (showRadioChannels) {
h += EVENT_PIXEL_HEIGHT;
}
if (showRadioOnoff) {
h += EVENT_PIXEL_HEIGHT;
}
if (showLeds) {
h += 3*LED_PIXEL_HEIGHT;
}
if (showLogOutputs) {
h += EVENT_PIXEL_HEIGHT;
}
if (showWatchpoints) {
h += EVENT_PIXEL_HEIGHT;
}
if (h != paintedMoteHeight) {
paintedMoteHeight = h;
timelineMoteRuler.repaint();
timeline.repaint();
}
}
public void closePlugin() {
/* Remove repaint timer */
repaintTimelineTimer.stop();
if (moteHighlightObserver != null) {
simulation.getCooja().deleteMoteHighlightObserver(moteHighlightObserver);
}
simulation.getEventCentral().removeMoteCountListener(newMotesListener);
/* Remove active mote interface observers */
for (MoteObservation o: activeMoteObservers) {
o.dispose();
}
activeMoteObservers.clear();
}
public Collection<Element> getConfigXML() {
ArrayList<Element> config = new ArrayList<Element>();
Element element;
/* Remember observed motes */
Mote[] allMotes = simulation.getMotes();
for (MoteEvents moteEvents: allMoteEvents) {
element = new Element("mote");
for (int i=0; i < allMotes.length; i++) {
if (allMotes[i] == moteEvents.mote) {
element.setText("" + i);
config.add(element);
break;
}
}
}
if (showRadioRXTX) {
element = new Element("showRadioRXTX");
config.add(element);
}
if (showRadioChannels) {
element = new Element("showRadioChannels");
config.add(element);
}
if (showRadioOnoff) {
element = new Element("showRadioHW");
config.add(element);
}
if (showLeds) {
element = new Element("showLEDs");
config.add(element);
}
if (showLogOutputs) {
element = new Element("showLogOutput");
config.add(element);
}
if (showWatchpoints) {
element = new Element("showWatchpoints");
config.add(element);
}
if (executionDetails) {
element = new Element("executionDetails");
config.add(element);
}
element = new Element("zoomfactor");
element.addContent("" + currentPixelDivisor);
config.add(element);
return config;
}
public boolean setConfigXML(Collection<Element> configXML, boolean visAvailable) {
showRadioRXTX = false;
showRadioChannels = false;
showRadioOnoff = false;
showLeds = false;
showLogOutputs = false;
showWatchpoints = false;
executionDetails = false;
/* Remove already registered motes */
MoteEvents[] allMoteEventsArr = allMoteEvents.toArray(new MoteEvents[0]);
for (MoteEvents moteEvents: allMoteEventsArr) {
removeMote(moteEvents.mote);
}
for (Element element : configXML) {
String name = element.getName();
if ("mote".equals(name)) {
int index = Integer.parseInt(element.getText());
addMote(simulation.getMote(index));
} else if ("showRadioRXTX".equals(name)) {
showRadioRXTX = true;
} else if ("showRadioChannels".equals(name)) {
showRadioChannels = true;
} else if ("showRadioHW".equals(name)) {
showRadioOnoff = true;
} else if ("showLEDs".equals(name)) {
showLeds = true;
} else if ("showLogOutput".equals(name)) {
showLogOutputs = true;
} else if ("showWatchpoints".equals(name)) {
showWatchpoints = true;
} else if ("executionDetails".equals(name)) {
executionDetails = true;
} else if ("zoom".equals(name)) {
/* NB: Historically this is a one-based not zero-based index */
final int zl = Integer.parseInt(element.getText())-1;
zoomFinishLevel(zl, 0, 0);
} else if ("zoomfactor".equals(name)) {
/* NB: Historically no validation on this option */
final double cpd = Double.parseDouble(element.getText());
zoomFinish(cpd, 0, 0);
}
}
recalculateMoteHeight();
return true;
}
private int mousePixelPositionX = -1;
private int mousePixelPositionY = -1;
private int mouseDownPixelPositionX = -1;
class Timeline extends JComponent {
private static final long serialVersionUID = 2206491823778169359L;
public Timeline() {
setLayout(null);
setToolTipText(null);
setBackground(COLOR_BACKGROUND);
addMouseListener(mouseAdapter);
addMouseMotionListener(mouseAdapter);
addMouseWheelListener(mouseAdapter);
/* Popup menu */
final JPopupMenu popupMenu = new JPopupMenu();
popupMenu.add(new JMenuItem(showInAllAction));
popupMenu.add(new JMenuItem(logListenerAction));
popupMenu.add(new JMenuItem(radioLoggerAction));
JMenu advancedMenu = new JMenu("Advanced");
advancedMenu.add(new JCheckBoxMenuItem(executionDetailsAction) {
private static final long serialVersionUID = 8314556794750277113L;
public boolean isSelected() {
return executionDetails;
}
});
addMouseListener(new MouseAdapter() {
long lastClick = -1;
public void mouseClicked(MouseEvent e) {
if (e.isPopupTrigger()) {
popupLocation = new Point(e.getX(), e.getY());
popupMenu.show(Timeline.this, e.getX(), e.getY());
}
/* Focus on double-click */
if (System.currentTimeMillis() - lastClick < 250) {
popupLocation = e.getPoint();
showInAllAction.actionPerformed(null);
long time = (long) (popupLocation.x*currentPixelDivisor);
Plugin[] plugins = simulation.getCooja().getStartedPlugins();
for (Plugin p: plugins) {
if (!(p instanceof TimeLine)) {
continue;
}
if (p == TimeLine.this) {
continue;
}
/* Select simulation time */
TimeLine plugin = (TimeLine) p;
plugin.trySelectTime(time);
}
}
lastClick = System.currentTimeMillis();
}
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) {
popupLocation = new Point(e.getX(), e.getY());
popupMenu.show(Timeline.this, e.getX(), e.getY());
}
}
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) {
popupLocation = new Point(e.getX(), e.getY());
popupMenu.show(Timeline.this, e.getX(), e.getY());
}
}
});
}
private MouseAdapter mouseAdapter = new MouseAdapter() {
private Popup popUpToolTip = null;
private double zoomInitialPixelDivisor;
private int zoomInitialMouseY;
private long zoomCenterTime = -1;
private double zoomCenter = -1;
public void mouseDragged(MouseEvent e) {
super.mouseDragged(e);
if (e.isControlDown()) {
/* Zoom with mouse */
if (zoomCenterTime < 0) {
return;
}
double factor = 0.01*(e.getY() - zoomInitialMouseY);
factor = Math.exp(factor);
final double cpd = zoomInitialPixelDivisor * factor;
zoomFinish(cpd, zoomCenterTime, zoomCenter);
return;
}
if (e.isAltDown()) {
/* Pan with mouse */
if (zoomCenterTime < 0) {
return;
}
zoomCenter = (double) (e.getX() - timeline.getVisibleRect().x) / timeline.getVisibleRect().width;
forceRepaintAndFocus(zoomCenterTime, zoomCenter);
return;
}
if (mousePixelPositionX >= 0) {
mousePixelPositionX = e.getX();
mousePixelPositionY = e.getY();
repaint();
}
}
public void mousePressed(MouseEvent e) {
if (e.isControlDown()) {
/* Zoom with mouse */
zoomInitialMouseY = e.getY();
zoomInitialPixelDivisor = currentPixelDivisor;
zoomCenterTime = (long) (e.getX()*currentPixelDivisor);
zoomCenter = (double) (e.getX() - timeline.getVisibleRect().x) / timeline.getVisibleRect().width;
return;
}
if (e.isAltDown()) {
/* Pan with mouse */
zoomCenterTime = (long) (e.getX()*currentPixelDivisor);
return;
}
if (popUpToolTip != null) {
popUpToolTip.hide();
popUpToolTip = null;
}
if (e.getPoint().getY() < FIRST_MOTE_PIXEL_OFFSET) {
mousePixelPositionX = e.getX();
mouseDownPixelPositionX = e.getX();
mousePixelPositionY = e.getY();
repaint();
} else {
/* Trigger tooltip */
JToolTip t = timeline.createToolTip();
t.setTipText(Timeline.this.getMouseToolTipText(e));
if (t.getTipText() == null || t.getTipText().equals("")) {
return;
}
t.validate();
/* Check tooltip width */
Rectangle screenBounds = timeline.getGraphicsConfiguration().getBounds();
int x;
{
int tooltip = e.getLocationOnScreen().x + t.getPreferredSize().width;
int screen = screenBounds.x + screenBounds.width;
if (tooltip > screen) {
x = e.getLocationOnScreen().x - (tooltip-screen);
} else {
x = e.getLocationOnScreen().x;
}
}
/* Check tooltip height */
int y;
{
int tooltip = e.getLocationOnScreen().y + t.getPreferredSize().height;
int screen = screenBounds.y + screenBounds.height;
if (tooltip > screen) {
y = e.getLocationOnScreen().y - (tooltip-screen);
} else {
y = e.getLocationOnScreen().y;
}
}
popUpToolTip = PopupFactory.getSharedInstance().getPopup(null, t, x, y);
popUpToolTip.show();
}
}
public void mouseReleased(MouseEvent e) {
zoomCenterTime = -1;
if (popUpToolTip != null) {
popUpToolTip.hide();
popUpToolTip = null;
}
super.mouseReleased(e);
mousePixelPositionX = -1;
repaint();
}
public void mouseWheelMoved(MouseWheelEvent e) {
if (e.isControlDown()) {
final int nticks = e.getWheelRotation();
final int zoomLevel = zoomGetLevel() + nticks;
final long zct = (long) (e.getX()*currentPixelDivisor);
final double zc = (double) (e.getX() - timeline.getVisibleRect().x) / timeline.getVisibleRect().width;
zoomFinishLevel(zoomLevel, zct, zc);
return;
}
}
};
private final Color SEPARATOR_COLOR = new Color(220, 220, 220);
public void paintComponent(Graphics g) {
Rectangle bounds = g.getClipBounds();
/*logger.info("Clip bounds: " + bounds);*/
if (needZoomOut) {
/* Need zoom out */
g.setColor(Color.RED);
g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
Rectangle vis = timeline.getVisibleRect();
g.setColor(Color.WHITE);
String msg = "Zoom out";
FontMetrics fm = g.getFontMetrics();
int msgWidth = fm.stringWidth(msg);
int msgHeight = fm.getHeight();
g.drawString(msg,
vis.x + vis.width/2 - msgWidth/2,
vis.y + vis.height/2 + msgHeight/2);
return;
}
long intervalStart = (long)(bounds.x*currentPixelDivisor);
long intervalEnd = (long) (intervalStart + bounds.width*currentPixelDivisor);
if (intervalEnd > simulation.getSimulationTime()) {
intervalEnd = simulation.getSimulationTime();
}
/*logger.info("Painting interval: " + intervalStart + " -> " + intervalEnd);*/
if (bounds.x > Integer.MAX_VALUE - 1000) {
/* Strange bounds */
return;
}
g.setColor(COLOR_BACKGROUND);
g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
drawTimeRule(g, intervalStart, intervalEnd);
/* Paint mote events */
int lineHeightOffset = FIRST_MOTE_PIXEL_OFFSET;
boolean dark = true;
for (int mIndex = 0; mIndex < allMoteEvents.size(); mIndex++) {
/* Mote separators */
if (dark) {
g.setColor(SEPARATOR_COLOR);
g.fillRect(
0, lineHeightOffset-2,
getWidth(), paintedMoteHeight
);
}
dark = !dark;
if (showRadioRXTX) {
MoteEvent firstEvent = getFirstIntervalEvent(allMoteEvents.get(mIndex).radioRXTXEvents, intervalStart);
if (firstEvent != null) {
firstEvent.paintInterval(g, lineHeightOffset, intervalEnd);
}
lineHeightOffset += EVENT_PIXEL_HEIGHT;
}
if (showRadioChannels) {
MoteEvent firstEvent = getFirstIntervalEvent(allMoteEvents.get(mIndex).radioChannelEvents, intervalStart);
if (firstEvent != null) {
firstEvent.paintInterval(g, lineHeightOffset, intervalEnd);
}
lineHeightOffset += EVENT_PIXEL_HEIGHT;
}
if (showRadioOnoff) {
MoteEvent firstEvent = getFirstIntervalEvent(allMoteEvents.get(mIndex).radioHWEvents, intervalStart);
if (firstEvent != null) {
firstEvent.paintInterval(g, lineHeightOffset, intervalEnd);
}
lineHeightOffset += EVENT_PIXEL_HEIGHT;
}
if (showLeds) {
MoteEvent firstEvent = getFirstIntervalEvent(allMoteEvents.get(mIndex).ledEvents, intervalStart);
if (firstEvent != null) {
firstEvent.paintInterval(g, lineHeightOffset, intervalEnd);
}
lineHeightOffset += 3*LED_PIXEL_HEIGHT;
}
if (showLogOutputs) {
MoteEvent firstEvent = getFirstIntervalEvent(allMoteEvents.get(mIndex).logEvents, intervalStart);
if (firstEvent != null) {
firstEvent.paintInterval(g, lineHeightOffset, intervalEnd);
}
lineHeightOffset += EVENT_PIXEL_HEIGHT;
}
if (showWatchpoints) {
MoteEvent firstEvent = getFirstIntervalEvent(allMoteEvents.get(mIndex).watchpointEvents, intervalStart);
if (firstEvent != null) {
firstEvent.paintInterval(g, lineHeightOffset, intervalEnd);
}
lineHeightOffset += EVENT_PIXEL_HEIGHT;
}
lineHeightOffset += EVENT_PIXEL_HEIGHT;
}
/* Draw vertical time marker (if mouse is dragged) */
drawMouseTime(g, intervalStart, intervalEnd);
}
private <T extends MoteEvent> T getFirstIntervalEvent(ArrayList<T> events, long time) {
/* TODO IMPLEMENT ME: Binary search */
int nrEvents = events.size();
if (nrEvents == 0) {
return null;
}
if (nrEvents == 1) {
events.get(0);
}
int ev = 0;
while (ev < nrEvents && events.get(ev).time < time) {
ev++;
}
ev--;
if (ev < 0) {
ev = 0;
}
if (ev >= events.size()) {
return events.get(events.size()-1);
}
return events.get(ev);
}
private void drawTimeRule(Graphics g, long start, long end) {
long time;
/* Paint 10ms and 100 ms markers */
g.setColor(Color.GRAY);
time = start - (start % (100*Simulation.MILLISECOND));
while (time <= end) {
if (time % (100*Simulation.MILLISECOND) == 0) {
g.drawLine(
(int) (time/currentPixelDivisor), 0,
(int) (time/currentPixelDivisor), TIME_MARKER_PIXEL_HEIGHT);
} else {
g.drawLine(
(int) (time/currentPixelDivisor), 0,
(int) (time/currentPixelDivisor), TIME_MARKER_PIXEL_HEIGHT/2);
}
time += (10*Simulation.MILLISECOND);
}
}
private void drawMouseTime(Graphics g, long start, long end) {
if (mousePixelPositionX >= 0) {
long time = (long) (mousePixelPositionX*currentPixelDivisor);
long diff = (long) (Math.abs(mouseDownPixelPositionX-mousePixelPositionX)*currentPixelDivisor);
String str =
"Time (ms): " + (double)time/Simulation.MILLISECOND +
" (" + (double)diff/Simulation.MILLISECOND + ")";
int h = g.getFontMetrics().getHeight();
int w = g.getFontMetrics().stringWidth(str) + 6;
int y= mousePixelPositionY<getHeight()/2?0:getHeight()-h;
int delta = mousePixelPositionX + w > end/currentPixelDivisor?w:0; /* Don't write outside visible area */
/* Line */
g.setColor(Color.GRAY);
g.drawLine(
mousePixelPositionX, 0,
mousePixelPositionX, getHeight());
/* Text box */
g.setColor(Color.DARK_GRAY);
g.fillRect(
mousePixelPositionX-delta, y,
w, h);
g.setColor(Color.BLACK);
g.drawRect(
mousePixelPositionX-delta, y,
w, h);
g.setColor(Color.WHITE);
g.drawString(str,
mousePixelPositionX+3-delta,
y+h-1);
}
}
public String getMouseToolTipText(MouseEvent event) {
if (event.getPoint().y <= TIME_MARKER_PIXEL_HEIGHT) {
return "<html>Click to display time marker</html>";
}
if (event.getPoint().y <= FIRST_MOTE_PIXEL_OFFSET) {
return null;
}
/* Mote */
int mote = (event.getPoint().y-FIRST_MOTE_PIXEL_OFFSET)/paintedMoteHeight;
if (mote < 0 || mote >= allMoteEvents.size()) {
return null;
}
String tooltip = "<html>Mote: " + allMoteEvents.get(mote).mote + "<br>";
/* Time */
long time = (long) (event.getPoint().x*currentPixelDivisor);
tooltip += "Time (ms): " + (double)time/Simulation.MILLISECOND + "<br>";
/* Event */
ArrayList<? extends MoteEvent> events = null;
int evMatched = 0;
int evMouse = ((event.getPoint().y-FIRST_MOTE_PIXEL_OFFSET) % paintedMoteHeight) / EVENT_PIXEL_HEIGHT;
if (showRadioRXTX) {
if (evMatched == evMouse) {
events = allMoteEvents.get(mote).radioRXTXEvents;
}
evMatched++;
}
if (showRadioChannels) {
if (evMatched == evMouse) {
events = allMoteEvents.get(mote).radioChannelEvents;
}
evMatched++;
}
if (showRadioOnoff) {
if (evMatched == evMouse) {
events = allMoteEvents.get(mote).radioHWEvents;
}
evMatched++;
}
if (showLeds) {
if (evMatched == evMouse) {
events = allMoteEvents.get(mote).ledEvents;
}
evMatched++;
}
if (showLogOutputs) {
if (evMatched == evMouse) {
events = allMoteEvents.get(mote).logEvents;
}
evMatched++;
}
if (showWatchpoints) {
if (evMatched == evMouse) {
events = allMoteEvents.get(mote).watchpointEvents;
}
evMatched++;
}
if (events != null) {
MoteEvent ev = getFirstIntervalEvent(events, time);
if (ev != null && time >= ev.time) {
tooltip += ev + "<br>";
if (ev.details != null) {
tooltip += "Details:<br>" + ev.details;
}
}
}
tooltip += "</html>";
return tooltip;
}
}
class MoteRuler extends JPanel {
private static final long serialVersionUID = -5555627354526272220L;
public MoteRuler() {
setPreferredSize(new Dimension(35, 1));
setToolTipText(null);
setBackground(COLOR_BACKGROUND);
final JPopupMenu popupMenu = new JPopupMenu();
final JMenuItem topItem = new JMenuItem(topMoteAction);
topItem.setText("Move to top");
popupMenu.add(topItem);
final JMenuItem sortItem = new JMenuItem(sortMoteAction);
sortItem.setText("Sort by distance");
popupMenu.add(sortItem);
final JMenuItem removeItem = new JMenuItem(removeMoteAction);
removeItem.setText("Remove from timeline");
popupMenu.add(removeItem);
final JMenuItem keepMoteOnlyItem = new JMenuItem(removeAllOtherMotesAction);
keepMoteOnlyItem.setText("Remove all motes from timeline but");
popupMenu.add(keepMoteOnlyItem);
addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
Mote m = getMote(e.getPoint());
if (m == null) {
return;
}
simulation.getCooja().signalMoteHighlight(m);
sortItem.setText("Sort by distance: " + m);
sortItem.putClientProperty("mote", m);
topItem.setText("Move to top: " + m);
topItem.putClientProperty("mote", m);
removeItem.setText("Remove from timeline: " + m);
removeItem.putClientProperty("mote", m);
keepMoteOnlyItem.setText("Remove all motes from timeline but: " + m);
keepMoteOnlyItem.putClientProperty("mote", m);
popupMenu.show(MoteRuler.this, e.getX(), e.getY());
}
});
}
private Mote getMote(Point p) {
if (p.y < FIRST_MOTE_PIXEL_OFFSET) {
return null;
}
int m = (p.y-FIRST_MOTE_PIXEL_OFFSET)/paintedMoteHeight;
if (m < allMoteEvents.size()) {
return allMoteEvents.get(m).mote;
}
return null;
}
protected void paintComponent(Graphics g) {
g.setColor(COLOR_BACKGROUND);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.BLACK);
g.setFont(new Font("SansSerif", Font.PLAIN, paintedMoteHeight));
int y = FIRST_MOTE_PIXEL_OFFSET-EVENT_PIXEL_HEIGHT/2+paintedMoteHeight;
for (MoteEvents moteLog: allMoteEvents) {
String str = "" + moteLog.mote.getID();
int w = g.getFontMetrics().stringWidth(str) + 1;
if (highlightedMotes.contains(moteLog.mote)) {
g.setColor(HIGHLIGHT_COLOR);
g.fillRect(0, y-paintedMoteHeight, getWidth()-1, paintedMoteHeight);
g.setColor(Color.BLACK);
}
g.drawString(str, getWidth() - w, y);
y += paintedMoteHeight;
}
}
public String getToolTipText(MouseEvent event) {
Point p = event.getPoint();
Mote m = getMote(p);
if (m == null)
return null;
return "<html>" + m + "<br>Click mote for options</html>";
}
}
/* Event classes */
abstract class MoteEvent {
MoteEvent prev = null;
MoteEvent next = null;
String details = null;
long time;
public MoteEvent(long time) {
this.time = time;
}
/**
* Used by the default paint method to color events.
* The event is not painted if the returned color is null.
*
* @see #paintInterval(Graphics, int, long)
* @return Event color or null
*/
public abstract Color getEventColor();
/* Default paint method */
public void paintInterval(Graphics g, int lineHeightOffset, long end) {
MoteEvent ev = this;
while (ev != null && ev.time < end) {
int w; /* Pixel width */
/* Calculate event width */
if (ev.next != null) {
w = (int) ((ev.next.time - ev.time)/currentPixelDivisor);
} else {
w = (int) ((end - ev.time)/currentPixelDivisor); /* No more events */
}
/* Handle zero pixel width events */
if (w == 0) {
if (PAINT_ZERO_WIDTH_EVENTS) {
w = 1;
} else {
ev = ev.next;
continue;
}
}
Color color = ev.getEventColor();
if (color == null) {
/* Skip painting event */
ev = ev.next;
continue;
}
g.setColor(color);
g.fillRect(
(int)(ev.time/currentPixelDivisor), lineHeightOffset,
w, EVENT_PIXEL_HEIGHT
);
ev = ev.next;
}
}
}
class NoHistoryEvent extends MoteEvent {
public NoHistoryEvent(long time) {
super(time);
}
public Color getEventColor() {
return Color.CYAN;
}
public String toString() {
return "No events has been captured yet";
}
}
public enum RXTXRadioEvent {
IDLE, RECEIVING, TRANSMITTING, INTERFERED
}
class RadioRXTXEvent extends MoteEvent {
RXTXRadioEvent state = null;
public RadioRXTXEvent(long time, RXTXRadioEvent ev) {
super(time);
this.state = ev;
}
public RadioRXTXEvent(long time, RXTXRadioEvent ev, String details) {
this(time, ev);
this.details = details;
}
public Color getEventColor() {
if (state == RXTXRadioEvent.IDLE) {
return null;
} else if (state == RXTXRadioEvent.TRANSMITTING) {
return Color.BLUE;
} else if (state == RXTXRadioEvent.RECEIVING) {
return Color.GREEN;
} else if (state == RXTXRadioEvent.INTERFERED) {
return Color.RED;
} else {
logger.fatal("Unknown RXTX event");
return null;
}
}
public String toString() {
if (state == RXTXRadioEvent.IDLE) {
return "Radio idle from " + time + "<br>";
} else if (state == RXTXRadioEvent.TRANSMITTING) {
return "Radio transmitting from " + time + "<br>";
} else if (state == RXTXRadioEvent.RECEIVING) {
return "Radio receiving from " + time + "<br>";
} else if (state == RXTXRadioEvent.INTERFERED) {
return "Radio interfered from " + time + "<br>";
} else {
return "Unknown event<br>";
}
}
}
private final static Color[] CHANNEL_COLORS = new Color[] {
Color.decode("0x008080"), Color.decode("0x808080"), Color.decode("0xC00000"),
Color.decode("0x000020"), Color.decode("0x202000"), Color.decode("0x200020"),
Color.decode("0x002020"), Color.decode("0x202020"), Color.decode("0x006060"),
Color.decode("0x606060"), Color.decode("0xA00000"), Color.decode("0x00A000"),
Color.decode("0x0000A0"), Color.decode("0x400040"), Color.decode("0x004040"),
Color.decode("0x404040"), Color.decode("0x200000"), Color.decode("0x002000"),
Color.decode("0xA0A000"), Color.decode("0xA000A0"), Color.decode("0x00A0A0"),
Color.decode("0xA0A0A0"), Color.decode("0xE00000"), Color.decode("0x600000"),
Color.decode("0x000040"), Color.decode("0x404000"), Color.decode("0xFF0000"),
Color.decode("0x00FF00"), Color.decode("0x0000FF"), Color.decode("0xFFFF00"),
Color.decode("0xFF00FF"), Color.decode("0x808000"), Color.decode("0x800080"),
};
class RadioChannelEvent extends MoteEvent {
int channel;
boolean radioOn;
public RadioChannelEvent(long time, int channel, boolean radioOn) {
super(time);
this.channel = channel;
this.radioOn = radioOn;
}
public Color getEventColor() {
if (channel >= 0) {
if (!radioOn) {
return null;
}
Color c = CHANNEL_COLORS[channel % CHANNEL_COLORS.length];
return c;
}
return null;
}
public String toString() {
String str = "Radio channel " + channel + "<br>";
return str;
}
}
class RadioHWEvent extends MoteEvent {
boolean on;
public RadioHWEvent(long time, boolean on) {
super(time);
this.on = on;
}
public RadioHWEvent(long time, boolean on, int channel) {
this(time, on);
}
public Color getEventColor() {
if (on) {
return Color.GRAY;
}
return null;
}
public String toString() {
String str = "Radio HW was turned " + (on?"on":"off") + "<br>";
return str;
}
}
class LEDEvent extends MoteEvent {
boolean red;
boolean green;
boolean blue;
Color color;
public LEDEvent(long time, boolean red, boolean green, boolean blue) {
super(time);
this.red = red;
this.green = green;
this.blue = blue;
this.color = new Color(red?255:0, green?255:0, blue?255:0);
}
public Color getEventColor() {
if (!red && !green && !blue) {
return null;
} else if (red && green && blue) {
return Color.LIGHT_GRAY;
} else {
return color;
}
}
/* LEDs are painted in three lines */
public void paintInterval(Graphics g, int lineHeightOffset, long end) {
MoteEvent ev = this;
while (ev != null && ev.time < end) {
int w; /* Pixel width */
/* Calculate event width */
if (ev.next != null) {
w = (int) ((ev.next.time - ev.time)/currentPixelDivisor);
} else {
w = (int) ((end - ev.time)/currentPixelDivisor); /* No more events */
}
/* Handle zero pixel width events */
if (w == 0) {
if (PAINT_ZERO_WIDTH_EVENTS) {
w = 1;
} else {
ev = ev.next;
continue;
}
}
Color color = ev.getEventColor();
if (color == null) {
/* Skip painting event */
ev = ev.next;
continue;
}
if (color.getRed() > 0) {
g.setColor(new Color(color.getRed(), 0, 0));
g.fillRect(
(int)(ev.time/currentPixelDivisor), lineHeightOffset,
w, LED_PIXEL_HEIGHT
);
}
if (color.getGreen() > 0) {
g.setColor(new Color(0, color.getGreen(), 0));
g.fillRect(
(int)(ev.time/currentPixelDivisor), lineHeightOffset+LED_PIXEL_HEIGHT,
w, LED_PIXEL_HEIGHT
);
}
if (color.getBlue() > 0) {
g.setColor(new Color(0, 0, color.getBlue()));
g.fillRect(
(int)(ev.time/currentPixelDivisor), lineHeightOffset+2*LED_PIXEL_HEIGHT,
w, LED_PIXEL_HEIGHT
);
}
ev = ev.next;
}
}
public String toString() {
return
"LED state:<br>" +
"Red = " + (red?"ON":"OFF") + "<br>" +
"Green = " + (green?"ON":"OFF") + "<br>" +
"Blue = " + (blue?"ON":"OFF") + "<br>";
}
}
class LogEvent extends MoteEvent {
LogOutputEvent logEvent;
public LogEvent(LogOutputEvent ev) {
super(ev.getTime());
this.logEvent = ev;
}
public Color getEventColor() {
if (logEventFilterPlugin != null) {
/* Ask log listener for event color to use */
return logEventFilterPlugin.getColorOfEntry(logEvent);
}
return Color.GRAY;
}
/* Default paint method */
public void paintInterval(Graphics g, int lineHeightOffset, long end) {
LogEvent ev = this;
while (ev != null && ev.time < end) {
/* Ask active log listener whether this should be filtered */
if (logEventFilterPlugin != null) {
boolean show = logEventFilterPlugin.filterWouldAccept(ev.logEvent);
if (!show) {
/* Skip painting event */
ev = (LogEvent) ev.next;
continue;
}
}
Color color = ev.getEventColor();
if (color == null) {
/* Skip painting event */
ev = (LogEvent) ev.next;
continue;
}
g.setColor(color);
g.fillRect(
(int)(ev.time/currentPixelDivisor), lineHeightOffset,
4, EVENT_PIXEL_HEIGHT
);
g.setColor(Color.BLACK);
g.fillRect(
(int)(ev.time/currentPixelDivisor), lineHeightOffset,
1, EVENT_PIXEL_HEIGHT
);
ev = (LogEvent) ev.next;
}
}
public String toString() {
return "Mote " + logEvent.getMote() + " says:<br>" + logEvent.getMessage() + "<br>";
}
}
class WatchpointEvent extends MoteEvent {
Watchpoint watchpoint;
public WatchpointEvent(long time, Watchpoint watchpoint) {
super(time);
this.watchpoint = watchpoint;
}
public Color getEventColor() {
Color c = watchpoint.getColor();
if (c == null) {
return Color.BLACK;
}
return c;
}
public String toString() {
String desc = watchpoint.getDescription();
desc = desc.replace("\n", "<br>");
return
"Watchpoint triggered at time (ms): " + time/Simulation.MILLISECOND + ".<br>"
+ desc + "<br>";
}
/* Default paint method */
public void paintInterval(Graphics g, int lineHeightOffset, long end) {
MoteEvent ev = this;
while (ev != null && ev.time < end) {
int w = 2; /* Watchpoints are always two pixels wide */
Color color = ev.getEventColor();
if (color == null) {
/* Skip painting event */
ev = ev.next;
continue;
}
g.setColor(color);
g.fillRect(
(int)(ev.time/currentPixelDivisor), lineHeightOffset,
w, EVENT_PIXEL_HEIGHT
);
ev = ev.next;
}
}
}
class MoteEvents {
Mote mote;
ArrayList<MoteEvent> radioRXTXEvents;
ArrayList<MoteEvent> radioChannelEvents;
ArrayList<MoteEvent> radioHWEvents;
ArrayList<MoteEvent> ledEvents;
ArrayList<MoteEvent> logEvents;
ArrayList<MoteEvent> watchpointEvents;
private MoteEvent lastRadioRXTXEvent = null;
private MoteEvent lastRadioChannelEvent = null;
private MoteEvent lastRadioHWEvent = null;
private MoteEvent lastLEDEvent = null;
private MoteEvent lastLogEvent = null;
private MoteEvent lastWatchpointEvent = null;
public MoteEvents(Mote mote) {
this.mote = mote;
this.radioRXTXEvents = new ArrayList<MoteEvent>();
this.radioChannelEvents = new ArrayList<MoteEvent>();
this.radioHWEvents = new ArrayList<MoteEvent>();
this.ledEvents = new ArrayList<MoteEvent>();
this.logEvents = new ArrayList<MoteEvent>();
this.watchpointEvents = new ArrayList<MoteEvent>();
if (mote.getSimulation().getSimulationTime() > 0) {
/* Create no history events */
lastRadioRXTXEvent = new NoHistoryEvent(0);
lastRadioChannelEvent = new NoHistoryEvent(0);
lastRadioHWEvent = new NoHistoryEvent(0);
lastLEDEvent = new NoHistoryEvent(0);
lastLogEvent = new NoHistoryEvent(0);
lastWatchpointEvent = new NoHistoryEvent(0);
radioRXTXEvents.add(lastRadioRXTXEvent);
radioChannelEvents.add(lastRadioChannelEvent);
radioHWEvents.add(lastRadioHWEvent);
ledEvents.add(lastLEDEvent);
logEvents.add(lastLogEvent);
watchpointEvents.add(lastWatchpointEvent);
}
}
protected void clear() {
this.radioRXTXEvents.clear();
this.radioChannelEvents.clear();
this.radioHWEvents.clear();
this.ledEvents.clear();
this.logEvents.clear();
this.watchpointEvents.clear();
if (mote.getSimulation().getSimulationTime() > 0) {
/* Create no history events */
lastRadioRXTXEvent = new NoHistoryEvent(0);
lastRadioChannelEvent = new NoHistoryEvent(0);
lastRadioHWEvent = new NoHistoryEvent(0);
lastLEDEvent = new NoHistoryEvent(0);
lastLogEvent = new NoHistoryEvent(0);
lastWatchpointEvent = new NoHistoryEvent(0);
radioRXTXEvents.add(lastRadioRXTXEvent);
radioChannelEvents.add(lastRadioChannelEvent);
radioHWEvents.add(lastRadioHWEvent);
ledEvents.add(lastLEDEvent);
logEvents.add(lastLogEvent);
watchpointEvents.add(lastWatchpointEvent);
}
}
public void addRadioRXTX(RadioRXTXEvent ev) {
/* Link with previous events */
if (lastRadioRXTXEvent != null) {
ev.prev = lastRadioRXTXEvent;
lastRadioRXTXEvent.next = ev;
}
lastRadioRXTXEvent = ev;
radioRXTXEvents.add(ev);
}
public void addRadioChannel(RadioChannelEvent ev) {
/* Link with previous events */
if (lastRadioChannelEvent != null) {
ev.prev = lastRadioChannelEvent;
lastRadioChannelEvent.next = ev;
}
lastRadioChannelEvent = ev;
radioChannelEvents.add(ev);
}
public void addRadioHW(RadioHWEvent ev) {
/* Link with previous events */
if (lastRadioHWEvent != null) {
ev.prev = lastRadioHWEvent;
lastRadioHWEvent.next = ev;
}
lastRadioHWEvent = ev;
radioHWEvents.add(ev);
}
public void addLED(LEDEvent ev) {
/* Link with previous events */
if (lastLEDEvent != null) {
ev.prev = lastLEDEvent;
lastLEDEvent.next = ev;
}
lastLEDEvent = ev;
ledEvents.add(ev);
}
public void addLog(LogEvent ev) {
/* Link with previous events */
if (lastLogEvent != null) {
ev.prev = lastLogEvent;
lastLogEvent.next = ev;
}
lastLogEvent = ev;
logEvents.add(ev);
}
public void addWatchpoint(WatchpointEvent ev) {
/* Link with previous events */
if (lastWatchpointEvent != null) {
ev.prev = lastWatchpointEvent;
lastWatchpointEvent.next = ev;
}
lastWatchpointEvent = ev;
watchpointEvents.add(ev);
}
}
private long lastRepaintSimulationTime = -1;
private Timer repaintTimelineTimer = new Timer(TIMELINE_UPDATE_INTERVAL, new ActionListener() {
public void actionPerformed(ActionEvent e) {
/* Only set new size if simulation time has changed */
long now = simulation.getSimulationTime();
if (now == lastRepaintSimulationTime) {
return;
}
lastRepaintSimulationTime = now;
/* Update timeline size */
int newWidth;
if (now/currentPixelDivisor > Integer.MAX_VALUE) {
/* Need zoom out */
newWidth = 1;
needZoomOut = true;
} else {
newWidth = (int) (now/currentPixelDivisor);
needZoomOut = false;
}
Rectangle visibleRectangle = timeline.getVisibleRect();
boolean isTracking = visibleRectangle.x + visibleRectangle.width >= timeline.getWidth();
int newHeight = (FIRST_MOTE_PIXEL_OFFSET + paintedMoteHeight * allMoteEvents.size());
timeline.setPreferredSize(new Dimension(
newWidth,
newHeight
));
timelineMoteRuler.setPreferredSize(new Dimension(
35,
newHeight
));
timeline.revalidate();
timeline.repaint();
/* Update visible rectangle */
if (isTracking) {
Rectangle r = new Rectangle(
newWidth-1, visibleRectangle.y,
1, 1);
timeline.scrollRectToVisible(r);
}
}
});
public String getQuickHelp() {
return
"<b>Timeline</b>" +
"<p>The timeline shows simulation events over time. " +
"The timeline can be used to inspect activities of individual nodes as well as interactions between nodes." +
"<p>For each mote, simulation events are shown on a colored line. Different colors correspond to different events. For more information about a particular event, mouse click it." +
"<p>The <i>Events</i> menu control what event types are shown in the timeline. " +
"Currently, six event types are supported (see below). " +
"<p>All motes are by default shown in the timeline. Motes can be removed from the timeline by right-clicking the node ID on the left." +
"<p>To display a vertical time marker on the timeline, press and hold the mouse on the time ruler (top)." +
"<p>For more options for a given event, right-click the mouse for a popup menu." +
"<p><b>Radio traffic</b>" +
"<br>Shows radio traffic events. Transmissions are painted blue, receptions are green, and interfered radios are red." +
"<p><b>Radio channel</b>" +
"<br>Shows the current radio channel by colors." +
"<p><b>Radio on/off</b>" +
"<br>Shows whether the mote radio is on or off. When gray, the radio is on." +
"<p><b>LEDs</b>" +
"<br>Shows LED state: red, green, and blue. (Assumes all mote types have exactly three LEDs.)" +
"<p><b>Log outputs</b>" +
"<br>Shows log outputs, as also shown in " + Cooja.getDescriptionOf(LogListener.class) +
"<p><b>Watchpoints</b>" +
"<br>Shows triggered watchpoints, currently only supported by emulated motes. To add watchpoints, use the Msp Code Watcher plugin.";
}
}