1992 lines
64 KiB
Java
1992 lines
64 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.
|
|
*
|
|
* $Id: TimeLine.java,v 1.25 2010/04/26 08:00:19 fros4943 Exp $
|
|
*/
|
|
|
|
package se.sics.cooja.plugins;
|
|
|
|
import java.awt.Color;
|
|
import java.awt.Component;
|
|
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.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 java.util.Vector;
|
|
|
|
import javax.swing.AbstractAction;
|
|
import javax.swing.Action;
|
|
import javax.swing.Box;
|
|
import javax.swing.JButton;
|
|
import javax.swing.JCheckBox;
|
|
import javax.swing.JComboBox;
|
|
import javax.swing.JComponent;
|
|
import javax.swing.JDialog;
|
|
import javax.swing.JFileChooser;
|
|
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.JSplitPane;
|
|
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 se.sics.cooja.ClassDescription;
|
|
import se.sics.cooja.GUI;
|
|
import se.sics.cooja.Mote;
|
|
import se.sics.cooja.PluginType;
|
|
import se.sics.cooja.Simulation;
|
|
import se.sics.cooja.VisPlugin;
|
|
import se.sics.cooja.Watchpoint;
|
|
import se.sics.cooja.WatchpointMote;
|
|
import se.sics.cooja.SimEventCentral.MoteCountListener;
|
|
import se.sics.cooja.interfaces.LED;
|
|
import se.sics.cooja.interfaces.Radio;
|
|
import se.sics.cooja.interfaces.Radio.RadioEvent;
|
|
|
|
/**
|
|
* 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 {
|
|
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 };
|
|
|
|
private boolean needZoomOut = false;
|
|
|
|
private static Logger logger = Logger.getLogger(TimeLine.class);
|
|
|
|
private int paintedMoteHeight = EVENT_PIXEL_HEIGHT;
|
|
|
|
private Simulation simulation;
|
|
private MoteCountListener newMotesListener;
|
|
|
|
private JScrollPane timelineScrollPane;
|
|
private MoteRuler timelineMoteRuler;
|
|
private JComponent timeline;
|
|
private Box eventCheckboxes;
|
|
private JSplitPane splitPane;
|
|
|
|
private ArrayList<MoteObservation> activeMoteObservers = new ArrayList<MoteObservation>();
|
|
|
|
private ArrayList<MoteEvents> allMoteEvents = new ArrayList<MoteEvents>();
|
|
|
|
private boolean showRadioRXTX = true;
|
|
private boolean showRadioChannels = false;
|
|
private boolean showRadioHW = true;
|
|
private boolean showLEDs = true;
|
|
private boolean showLogOutputs = false;
|
|
private boolean showWatchpoints = false;
|
|
|
|
private Point popupLocation = null;
|
|
|
|
/**
|
|
* @param simulation Simulation
|
|
* @param gui GUI
|
|
*/
|
|
public TimeLine(final Simulation simulation, final GUI gui) {
|
|
super("Timeline (Add motes to observe by clicking +)", gui);
|
|
this.simulation = simulation;
|
|
|
|
currentPixelDivisor = ZOOM_LEVELS[ZOOM_LEVELS.length/2];
|
|
|
|
/* Box: events to observe */
|
|
eventCheckboxes = Box.createVerticalBox();
|
|
JCheckBox eventCheckBox;
|
|
eventCheckBox = createEventCheckbox("Radio RX/TX", "Show radio transmissions, receptions, and collisions");
|
|
eventCheckBox.setSelected(showRadioRXTX);
|
|
eventCheckBox.setName("showRadioRXTX");
|
|
eventCheckBox.addActionListener(new ActionListener() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
showRadioRXTX = ((JCheckBox) e.getSource()).isSelected();
|
|
recalculateMoteHeight();
|
|
}
|
|
});
|
|
eventCheckboxes.add(eventCheckBox);
|
|
eventCheckBox = createEventCheckbox("Radio channels", "Show different radio channels");
|
|
eventCheckBox.setSelected(showRadioChannels);
|
|
eventCheckBox.setName("showRadioChannels");
|
|
eventCheckBox.addActionListener(new ActionListener() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
showRadioChannels = ((JCheckBox) e.getSource()).isSelected();
|
|
recalculateMoteHeight();
|
|
}
|
|
});
|
|
/*eventCheckboxes.add(eventCheckBox);*/
|
|
eventCheckBox = createEventCheckbox("Radio ON/OFF", "Show radio hardware state");
|
|
eventCheckBox.setSelected(showRadioHW);
|
|
eventCheckBox.setName("showRadioHW");
|
|
eventCheckBox.addActionListener(new ActionListener() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
showRadioHW = ((JCheckBox) e.getSource()).isSelected();
|
|
recalculateMoteHeight();
|
|
}
|
|
});
|
|
eventCheckboxes.add(eventCheckBox);
|
|
eventCheckBox = createEventCheckbox("LEDs", "Show LED state");
|
|
eventCheckBox.setSelected(showLEDs);
|
|
eventCheckBox.setName("showLEDs");
|
|
eventCheckBox.addActionListener(new ActionListener() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
showLEDs = ((JCheckBox) e.getSource()).isSelected();
|
|
recalculateMoteHeight();
|
|
}
|
|
});
|
|
eventCheckboxes.add(eventCheckBox);
|
|
eventCheckBox = createEventCheckbox("Log output", "Show mote log output, such as by printf()'s");
|
|
eventCheckBox.setSelected(showLogOutputs);
|
|
eventCheckBox.setName("showLogOutput");
|
|
eventCheckBox.addActionListener(new ActionListener() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
showLogOutputs = ((JCheckBox) e.getSource()).isSelected();
|
|
recalculateMoteHeight();
|
|
}
|
|
});
|
|
/*eventCheckboxes.add(eventCheckBox);*/
|
|
eventCheckBox = createEventCheckbox("Watchpoints", "Show code watchpoints (for MSPSim-based motes)");
|
|
eventCheckBox.setSelected(showWatchpoints);
|
|
eventCheckBox.setName("showWatchpoints");
|
|
eventCheckBox.addActionListener(new ActionListener() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
showWatchpoints = ((JCheckBox) e.getSource()).isSelected();
|
|
recalculateMoteHeight();
|
|
}
|
|
});
|
|
eventCheckboxes.add(eventCheckBox);
|
|
|
|
/* 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);
|
|
JButton timelineAddMoteButton = new JButton(addMoteAction);
|
|
timelineAddMoteButton.setText("+");
|
|
timelineAddMoteButton.setToolTipText("Add mote");
|
|
timelineAddMoteButton.setBorderPainted(false);
|
|
timelineAddMoteButton.setFont(new Font("SansSerif", Font.PLAIN, 11));
|
|
|
|
timelineMoteRuler = new MoteRuler();
|
|
timelineScrollPane.setRowHeaderView(timelineMoteRuler);
|
|
timelineScrollPane.setCorner(JScrollPane.LOWER_LEFT_CORNER, timelineAddMoteButton);
|
|
timelineScrollPane.setBackground(Color.WHITE);
|
|
|
|
splitPane = new JSplitPane(
|
|
JSplitPane.HORIZONTAL_SPLIT,
|
|
new JScrollPane(eventCheckboxes),
|
|
timelineScrollPane
|
|
);
|
|
splitPane.setOneTouchExpandable(true);
|
|
|
|
/* Zoom in/out via keyboard*/
|
|
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, KeyEvent.CTRL_DOWN_MASK), "zoomIn");
|
|
getActionMap().put("zoomIn", zoomInAction);
|
|
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, KeyEvent.CTRL_DOWN_MASK), "zoomOut");
|
|
getActionMap().put("zoomOut", zoomOutAction);
|
|
|
|
getContentPane().add(splitPane);
|
|
|
|
pack();
|
|
setSize(gui.getDesktopPane().getWidth(), 150);
|
|
setLocation(0, gui.getDesktopPane().getHeight() - 150);
|
|
|
|
numberMotesWasUpdated();
|
|
|
|
/* Automatically add/delete motes */
|
|
simulation.getEventCentral().addMoteCountListener(newMotesListener = new MoteCountListener() {
|
|
public void moteWasAdded(Mote mote) {
|
|
addMote(mote);
|
|
}
|
|
public void moteWasRemoved(Mote mote) {
|
|
removeMote(mote);
|
|
}
|
|
});
|
|
for (Mote m: simulation.getMotes()) {
|
|
addMote(m);
|
|
}
|
|
|
|
/* Update timeline for the duration of the plugin */
|
|
repaintTimelineTimer.start();
|
|
|
|
/* TODO Register Integer overflow time event */
|
|
}
|
|
|
|
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 addMoteAction = new AbstractAction("Add motes to timeline") {
|
|
private static final long serialVersionUID = 7546685285707302865L;
|
|
public void actionPerformed(ActionEvent e) {
|
|
|
|
JComboBox source = new JComboBox();
|
|
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", "Add"};
|
|
optionPane.setOptions(options);
|
|
optionPane.setInitialValue(options[1]);
|
|
JDialog dialog = optionPane.createDialog(GUI.getTopParentContainer(), "Add mote to timeline");
|
|
dialog.setVisible(true);
|
|
|
|
if (optionPane.getValue() == null || !optionPane.getValue().equals("Add")) {
|
|
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 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);
|
|
|
|
int zoomLevel = 0;
|
|
while (zoomLevel < ZOOM_LEVELS.length) {
|
|
if (currentPixelDivisor <= ZOOM_LEVELS[zoomLevel]) break;
|
|
zoomLevel++;
|
|
}
|
|
|
|
if (zoomLevel > 0) {
|
|
zoomLevel--; /* zoom in */
|
|
}
|
|
currentPixelDivisor = ZOOM_LEVELS[zoomLevel];
|
|
logger.info("Zoom level: " + currentPixelDivisor + " microseconds/pixel " + ((zoomLevel==0)?"(MIN)":""));
|
|
|
|
forceRepaintAndFocus(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;
|
|
}
|
|
final long centerTime = (long) (pixelX*currentPixelDivisor);
|
|
if (mousePixelPositionX > 0) {
|
|
pixelX = mousePixelPositionX;
|
|
}
|
|
|
|
int zoomLevel = 0;
|
|
while (zoomLevel < ZOOM_LEVELS.length) {
|
|
if (currentPixelDivisor <= ZOOM_LEVELS[zoomLevel]) break;
|
|
zoomLevel++;
|
|
}
|
|
|
|
if (zoomLevel < ZOOM_LEVELS.length-1) {
|
|
zoomLevel++; /* zoom out */
|
|
}
|
|
currentPixelDivisor = ZOOM_LEVELS[zoomLevel];
|
|
logger.info("Zoom level: " + currentPixelDivisor + " microseconds/pixel " + ((zoomLevel==ZOOM_LEVELS.length-1)?"(MAX)":""));
|
|
|
|
forceRepaintAndFocus(centerTime, 0.5);
|
|
}
|
|
};
|
|
|
|
private Action zoomSliderAction = new AbstractAction("Zoom slider (Ctrl+Mouse)") {
|
|
private static final long serialVersionUID = -4288046377707363837L;
|
|
public void actionPerformed(ActionEvent e) {
|
|
int zoomLevel = 0;
|
|
while (zoomLevel < ZOOM_LEVELS.length) {
|
|
if (currentPixelDivisor <= ZOOM_LEVELS[zoomLevel]) break;
|
|
zoomLevel++;
|
|
}
|
|
|
|
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) {
|
|
int zoomLevel = zoomSlider.getValue();
|
|
|
|
currentPixelDivisor = ZOOM_LEVELS[zoomLevel];
|
|
logger.info("Zoom level: " + currentPixelDivisor + " microseconds/pixel " + ((zoomLevel==ZOOM_LEVELS.length-1)?"(MAX)":""));
|
|
|
|
forceRepaintAndFocus(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 raw data to file") {
|
|
private static final long serialVersionUID = 975176793514425718L;
|
|
public void actionPerformed(ActionEvent e) {
|
|
JFileChooser fc = new JFileChooser();
|
|
int returnVal = fc.showSaveDialog(GUI.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(
|
|
GUI.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 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++;
|
|
}
|
|
}
|
|
|
|
/* TODO Radio channels */
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* TODO Watchpoints */
|
|
|
|
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("to Radio Logger") {
|
|
private static final long serialVersionUID = 7690116136861949864L;
|
|
public void actionPerformed(ActionEvent e) {
|
|
RadioLogger plugin = (RadioLogger) simulation.getGUI().getStartedPlugin(RadioLogger.class.getName());
|
|
if (plugin == null) {
|
|
logger.fatal("No Radio Logger plugin");
|
|
return;
|
|
}
|
|
if (popupLocation == null) {
|
|
return;
|
|
}
|
|
|
|
/* Select simulation time */
|
|
plugin.trySelectTime((long) (popupLocation.x*currentPixelDivisor));
|
|
}
|
|
};
|
|
private Action logListenerAction = new AbstractAction("to Log Listener") {
|
|
private static final long serialVersionUID = -8626118368774023257L;
|
|
public void actionPerformed(ActionEvent e) {
|
|
LogListener plugin = (LogListener) simulation.getGUI().getStartedPlugin(LogListener.class.getName());
|
|
if (plugin == null) {
|
|
logger.fatal("No Log Listener plugin");
|
|
return;
|
|
}
|
|
if (popupLocation == null) {
|
|
return;
|
|
}
|
|
|
|
/* Select simulation time */
|
|
plugin.trySelectTime((long) (popupLocation.x*currentPixelDivisor));
|
|
}
|
|
};
|
|
|
|
private void numberMotesWasUpdated() {
|
|
/* Plugin title */
|
|
if (allMoteEvents.isEmpty()) {
|
|
setTitle("Timeline (Add motes to observe by clicking +)");
|
|
} else {
|
|
setTitle("Timeline (" + 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 ActionListener 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, ActionListener 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(Mote mote, final MoteEvents moteEvents) {
|
|
/* TODO Log: final Log moteLog = mote.getInterfaces().getLog(); */
|
|
/* TODO Unknown state event */
|
|
|
|
/* 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 HW, RXTX */
|
|
final Radio moteRadio = mote.getInterfaces().getRadio();
|
|
if (moteRadio != null) {
|
|
RadioHWEvent startupHW = new RadioHWEvent(
|
|
simulation.getSimulationTime(), moteRadio.isReceiverOn());
|
|
moteEvents.addRadioHW(startupHW);
|
|
RadioRXTXEvent startupRXTX = new RadioRXTXEvent(
|
|
simulation.getSimulationTime(), RXTXRadioEvent.IDLE);
|
|
moteEvents.addRadioRXTX(startupRXTX);
|
|
Observer observer = new Observer() {
|
|
public void update(Observable o, Object arg) {
|
|
/* Radio HW events */
|
|
if (moteRadio.getLastEvent() == RadioEvent.HW_ON ||
|
|
moteRadio.getLastEvent() == RadioEvent.HW_OFF) {
|
|
RadioHWEvent ev = new RadioHWEvent(
|
|
simulation.getSimulationTime(), moteRadio.getLastEvent()==RadioEvent.HW_ON);
|
|
|
|
moteEvents.addRadioHW(ev);
|
|
return;
|
|
}
|
|
|
|
/* Radio RXTX events */
|
|
RadioEvent radioEv = moteRadio.getLastEvent();
|
|
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.isReceiverOn()) {
|
|
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);
|
|
return;
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
moteRadio.addObserver(observer);
|
|
activeMoteObservers.add(new MoteObservation(mote, moteRadio, observer));
|
|
}
|
|
|
|
/* XXX Experimental: Watchpoints */
|
|
if (mote instanceof WatchpointMote) {
|
|
final WatchpointMote watchpointMote = ((WatchpointMote)mote);
|
|
ActionListener listener = new ActionListener() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
if (watchpointMote.getLastWatchpoint() == null) {
|
|
return;
|
|
}
|
|
WatchpointEvent ev = new WatchpointEvent(
|
|
simulation.getSimulationTime(),
|
|
watchpointMote.getLastWatchpoint()
|
|
);
|
|
|
|
moteEvents.addWatchpoint(ev);
|
|
}
|
|
};
|
|
|
|
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 (showRadioHW) {
|
|
h += EVENT_PIXEL_HEIGHT;
|
|
}
|
|
if (showLEDs) {
|
|
h += 3*LED_PIXEL_HEIGHT;
|
|
}
|
|
if (showLogOutputs) {
|
|
h += EVENT_PIXEL_HEIGHT;
|
|
}
|
|
if (showWatchpoints) {
|
|
h += EVENT_PIXEL_HEIGHT;
|
|
}
|
|
paintedMoteHeight = h;
|
|
timelineMoteRuler.repaint();
|
|
timeline.repaint();
|
|
}
|
|
|
|
public void closePlugin() {
|
|
/* Remove repaint timer */
|
|
repaintTimelineTimer.stop();
|
|
|
|
simulation.getEventCentral().removeMoteCountListener(newMotesListener);
|
|
|
|
/* Remove active mote interface observers */
|
|
for (MoteObservation o: activeMoteObservers) {
|
|
o.dispose();
|
|
}
|
|
activeMoteObservers.clear();
|
|
}
|
|
|
|
public Collection<Element> getConfigXML() {
|
|
Vector<Element> config = new Vector<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 (showRadioHW) {
|
|
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);
|
|
}
|
|
|
|
element = new Element("split");
|
|
element.addContent("" + splitPane.getDividerLocation());
|
|
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;
|
|
showRadioHW = false;
|
|
showLEDs = false;
|
|
showLogOutputs = false;
|
|
showWatchpoints = 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)) {
|
|
showRadioHW = true;
|
|
} else if ("showLEDs".equals(name)) {
|
|
showLEDs = true;
|
|
} else if ("showLogOutput".equals(name)) {
|
|
showLogOutputs = true;
|
|
} else if ("showWatchpoints".equals(name)) {
|
|
showWatchpoints = true;
|
|
} else if ("split".equals(name)) {
|
|
splitPane.setDividerLocation(Integer.parseInt(element.getText()));
|
|
} else if ("zoom".equals(name)) {
|
|
currentPixelDivisor = ZOOM_LEVELS[Integer.parseInt(element.getText())-1];
|
|
forceRepaintAndFocus(0, 0);
|
|
} else if ("zoomfactor".equals(name)) {
|
|
currentPixelDivisor = Double.parseDouble(element.getText());
|
|
forceRepaintAndFocus(0, 0);
|
|
}
|
|
}
|
|
|
|
/* XXX HACK: Update checkboxes according to config */
|
|
for (Component c: eventCheckboxes.getComponents()) {
|
|
if (c.getName() == "showRadioRXTX") {
|
|
((JCheckBox)c).setSelected(showRadioRXTX);
|
|
} else if (c.getName() == "showRadioChannels") {
|
|
((JCheckBox)c).setSelected(showRadioChannels);
|
|
} else if (c.getName() == "showRadioHW") {
|
|
((JCheckBox)c).setSelected(showRadioHW);
|
|
} else if (c.getName() == "showLEDs") {
|
|
((JCheckBox)c).setSelected(showLEDs);
|
|
} else if (c.getName() == "showLogOutput") {
|
|
((JCheckBox)c).setSelected(showLogOutputs);
|
|
} else if (c.getName() == "showWatchpoints") {
|
|
((JCheckBox)c).setSelected(showWatchpoints);
|
|
}
|
|
}
|
|
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);
|
|
|
|
/* Popup menu */
|
|
final JPopupMenu popupMenu = new JPopupMenu();
|
|
|
|
popupMenu.add(new JMenuItem(addMoteAction));
|
|
|
|
popupMenu.addSeparator();
|
|
|
|
popupMenu.add(new JMenuItem(zoomInAction));
|
|
popupMenu.add(new JMenuItem(zoomOutAction));
|
|
popupMenu.add(new JMenuItem(zoomSliderAction));
|
|
|
|
popupMenu.addSeparator();
|
|
|
|
popupMenu.add(new JMenuItem(saveDataAction));
|
|
popupMenu.add(new JMenuItem(statisticsAction));
|
|
|
|
popupMenu.addSeparator();
|
|
|
|
popupMenu.add(new JMenuItem(radioLoggerAction));
|
|
popupMenu.add(new JMenuItem(logListenerAction));
|
|
|
|
addMouseListener(new MouseAdapter() {
|
|
public void mouseClicked(MouseEvent e) {
|
|
if (e.isPopupTrigger()) {
|
|
popupLocation = new Point(e.getX(), e.getY());
|
|
popupMenu.show(Timeline.this, e.getX(), e.getY());
|
|
}
|
|
}
|
|
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);
|
|
|
|
currentPixelDivisor = zoomInitialPixelDivisor * factor;
|
|
if (currentPixelDivisor < ZOOM_LEVELS[0]) {
|
|
currentPixelDivisor = ZOOM_LEVELS[0];
|
|
}
|
|
if (currentPixelDivisor > ZOOM_LEVELS[ZOOM_LEVELS.length-1]) {
|
|
currentPixelDivisor = ZOOM_LEVELS[ZOOM_LEVELS.length-1];
|
|
}
|
|
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 (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;
|
|
}
|
|
popUpToolTip = PopupFactory.getSharedInstance().getPopup(timeline, t, e.getXOnScreen(), e.getYOnScreen());
|
|
popUpToolTip.show();
|
|
}
|
|
}
|
|
public void mouseReleased(MouseEvent e) {
|
|
zoomCenterTime = -1;
|
|
if (popUpToolTip != null) {
|
|
popUpToolTip.hide();
|
|
popUpToolTip = null;
|
|
}
|
|
super.mouseReleased(e);
|
|
mousePixelPositionX = -1;
|
|
repaint();
|
|
}
|
|
};
|
|
|
|
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) {
|
|
/* TODO 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 (showRadioHW) {
|
|
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), (int)0,
|
|
(int) (time/currentPixelDivisor), (int)TIME_MARKER_PIXEL_HEIGHT);
|
|
} else {
|
|
g.drawLine(
|
|
(int) (time/currentPixelDivisor), (int)0,
|
|
(int) (time/currentPixelDivisor), (int)TIME_MARKER_PIXEL_HEIGHT/2);
|
|
}
|
|
time += (10*Simulation.MILLISECOND);
|
|
}
|
|
}
|
|
|
|
private void drawMouseTime(Graphics g, long start, long end) {
|
|
if (mousePixelPositionX >= 0) {
|
|
String str =
|
|
"Time (ms): " +
|
|
((double)mousePixelPositionX*currentPixelDivisor/Simulation.MILLISECOND) +
|
|
" (" + Math.abs(((double)(mouseDownPixelPositionX - mousePixelPositionX)*currentPixelDivisor/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 = event.getPoint().x*(long)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 (showRadioHW) {
|
|
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>";
|
|
}
|
|
}
|
|
|
|
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 removeItem = new JMenuItem(removeMoteAction);
|
|
removeItem.setText("Remove from timeline");
|
|
popupMenu.add(removeItem);
|
|
|
|
addMouseListener(new MouseAdapter() {
|
|
public void mouseClicked(MouseEvent e) {
|
|
Mote m = getMote(e.getPoint());
|
|
if (m == null) {
|
|
return;
|
|
}
|
|
removeItem.setText("Remove from timeline: " + m);
|
|
removeItem.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;
|
|
|
|
/*g.drawRect(0, y, getWidth()-1, paintedMoteHeight);*/
|
|
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;
|
|
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 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>";
|
|
}
|
|
}
|
|
}
|
|
class RadioChannelEvent extends MoteEvent {
|
|
public RadioChannelEvent(long time) {
|
|
super(time);
|
|
}
|
|
public Color getEventColor() {
|
|
return Color.GRAY; /* TODO Implement me */
|
|
}
|
|
}
|
|
class RadioHWEvent extends MoteEvent {
|
|
boolean on;
|
|
public RadioHWEvent(long time, boolean on) {
|
|
super(time);
|
|
this.on = on;
|
|
}
|
|
public Color getEventColor() {
|
|
return on?Color.GRAY:null;
|
|
}
|
|
public String toString() {
|
|
return "Radio HW was turned " + (on?"on":"off") + " at time " + time + "<br>";
|
|
}
|
|
}
|
|
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 {
|
|
public LogEvent(long time) {
|
|
super(time);
|
|
}
|
|
public Color getEventColor() {
|
|
return Color.GRAY; /* TODO Implement me */
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
/* TODO XXX Requires MSPSim changes */
|
|
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 = (int) (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);
|
|
}
|
|
}
|
|
});
|
|
|
|
}
|