From ad2c9528102f4e06b4825fdf296eaea50728fa97 Mon Sep 17 00:00:00 2001 From: fros4943 Date: Mon, 4 May 2009 15:38:35 +0000 Subject: [PATCH] new cooja timeline plugin. still experimental and under development, therefore not yet enabled by default --- .../java/se/sics/cooja/plugins/TimeLine.java | 1276 +++++++++++++++++ 1 file changed, 1276 insertions(+) create mode 100644 tools/cooja/java/se/sics/cooja/plugins/TimeLine.java diff --git a/tools/cooja/java/se/sics/cooja/plugins/TimeLine.java b/tools/cooja/java/se/sics/cooja/plugins/TimeLine.java new file mode 100644 index 000000000..944e155eb --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/plugins/TimeLine.java @@ -0,0 +1,1276 @@ +/* + * 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.1 2009/05/04 15:38:35 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.Graphics; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.*; + +import javax.swing.*; + +import org.apache.log4j.Logger; +import org.jdom.Element; +import se.sics.cooja.*; +import se.sics.cooja.interfaces.LED; +import se.sics.cooja.interfaces.Log; +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 = 1L; + private static Logger logger = Logger.getLogger(TimeLine.class); + + private static Color COLOR_BACKGROUND = Color.WHITE; + 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 int paintedMoteHeight = EVENT_PIXEL_HEIGHT; + + private Simulation simulation; + private JScrollPane timelineScrollPane; + private MoteRuler timelineMoteRuler; + private JPanel timeline; + private Box eventCheckboxes; + + private Observer tickObserver; + private ArrayList activeMoteObservers = new ArrayList(); + + private ArrayList allMoteEvents = new ArrayList(); + + private long startTime; + + private boolean viewportTracking = true; + private Point viewportInfinite = new Point(Integer.MAX_VALUE, 0); + private Runnable updateViewport = new Runnable() { + public void run() { + if (!viewportTracking) { + return; + } + timelineScrollPane.getViewport().setViewPosition(viewportInfinite); + } + }; + + private boolean showRadioRXTX = true; + private boolean showRadioChannels = false; + private boolean showRadioHW = true; + private boolean showLEDs = true; + private boolean showLogOutputs = false; + private boolean showWatchpoints = false; + + /** + * @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; + startTime = simulation.getSimulationTime(); + + /* Automatically repaint every tick */ + simulation.addTickObserver(tickObserver = new Observer() { + public void update(Observable obs, Object obj) { + if (timelineScrollPane == null) + return; + + timeline.setPreferredSize(new Dimension( + (int) (simulation.getSimulationTime() - startTime), + (int) (FIRST_MOTE_PIXEL_OFFSET + paintedMoteHeight * allMoteEvents.size()) + )); + timelineMoteRuler.setPreferredSize(new Dimension( + 35, + (int) (FIRST_MOTE_PIXEL_OFFSET + paintedMoteHeight * allMoteEvents.size()) + )); + timeline.revalidate(); + timeline.repaint(); + + if (viewportTracking) { + SwingUtilities.invokeLater(updateViewport); + } + } + }); + + /* Box: events to observe */ + eventCheckboxes = Box.createVerticalBox(); + JCheckBox eventCheckBox; + eventCheckBox = createEventCheckbox("Radio RX/TX", "Show radio transmissions, receptions, and collisions"); + 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.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.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.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.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 configurable on MSPSim based motes"); + 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().addMouseMotionListener(new MouseAdapter() { + public void mouseDragged(MouseEvent e) { + Rectangle view = timelineScrollPane.getViewport().getViewRect(); + if (view.x + view.width + 5 > simulation.getSimulationTime()) { + viewportInfinite.y = view.y; + viewportTracking = true; + } else { + viewportTracking = false; + } + timeline.revalidate(); + timeline.repaint(); + } + }); + timelineScrollPane.getVerticalScrollBar().addMouseMotionListener(new MouseAdapter() { + public void mouseDragged(MouseEvent e) { + Rectangle view = timelineScrollPane.getViewport().getViewRect(); + viewportInfinite.y = view.y; + timeline.revalidate(); + timeline.repaint(); + } + }); + + 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); + + JSplitPane splitPane = new JSplitPane( + JSplitPane.HORIZONTAL_SPLIT, + eventCheckboxes, + timelineScrollPane + ); + splitPane.setOneTouchExpandable(true); + splitPane.setResizeWeight(0.0); + + getContentPane().add(splitPane); + + pack(); + setSize(gui.getDesktopPane().getWidth(), 150); + setLocation(0, gui.getDesktopPane().getHeight() - 150); + + numberMotesWasUpdated(); + } + + private JCheckBox createEventCheckbox(String text, String tooltip) { + JCheckBox checkBox = new JCheckBox(text, true); + checkBox.setToolTipText(tooltip); + return checkBox; + } + + private Action removeMoteAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + JComponent b = (JComponent) e.getSource(); + Mote m = (Mote) b.getClientProperty("mote"); + removeMote(m); + } + }; + private Action addMoteAction = new AbstractAction() { + 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 numberMotesWasUpdated() { + /* Timeline */ + timeline.setPreferredSize(new Dimension( + (int) (simulation.getSimulationTime() - startTime), + (int) (FIRST_MOTE_PIXEL_OFFSET + paintedMoteHeight * allMoteEvents.size()) + )); + timelineMoteRuler.setPreferredSize(new Dimension( + 35, + (int) (FIRST_MOTE_PIXEL_OFFSET + paintedMoteHeight * allMoteEvents.size()) + )); + timelineMoteRuler.revalidate(); + timelineMoteRuler.repaint(); + timeline.revalidate(); + timeline.repaint(); + + /* Plugin title */ + if (allMoteEvents.isEmpty()) { + setTitle("Timeline (Add motes to observe by clicking +)"); + } else { + setTitle("Timeline (" + allMoteEvents.size() + " motes)"); + } + } + + /* XXX Keeps track of observed mote interfaces */ + class MoteObservation { + private Observer observer; + private Observable observable; + private Mote mote; + + public MoteObservation(Mote mote, Observable observable, Observer observer) { + this.mote = mote; + this.observable = observable; + this.observer = observer; + } + + public Mote getMote() { + return mote; + } + + /** + * Disconnect observer from observable (stop observing) and clean up resources (remove pointers). + */ + public void dispose() { + observable.deleteObserver(observer); + mote = null; + observable = null; + observer = null; + } + } + + private void addMoteObservers(Mote mote, final MoteEvents moteEvents) { + final LED moteLEDs = mote.getInterfaces().getLED(); + final Radio moteRadio = mote.getInterfaces().getRadio(); + final Log moteLog = mote.getInterfaces().getLog(); + /* TODO Watchpoints? */ + + /* LEDs */ + 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 */ + if (moteRadio != null) { + RadioHWEvent startupHW = new RadioHWEvent( + simulation.getSimulationTime(), moteRadio.isReceiverOn()); + moteEvents.addRadioHW(startupHW); + RadioRXTXEvent startupRXTX = new RadioRXTXEvent( + simulation.getSimulationTime(), RadioEvent.UNKNOWN); + 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 */ + if (moteRadio.getLastEvent() == RadioEvent.TRANSMISSION_STARTED || + moteRadio.getLastEvent() == RadioEvent.TRANSMISSION_FINISHED || + moteRadio.getLastEvent() == RadioEvent.RECEPTION_STARTED || + moteRadio.getLastEvent() == RadioEvent.RECEPTION_INTERFERED || + moteRadio.getLastEvent() == RadioEvent.RECEPTION_FINISHED) { + RadioRXTXEvent ev = new RadioRXTXEvent( + simulation.getSimulationTime(), moteRadio.getLastEvent()); + + moteEvents.addRadioRXTX(ev); + return; + } + + } + }; + + moteRadio.addObserver(observer); + activeMoteObservers.add(new MoteObservation(mote, moteRadio, observer)); + } + + } + + private void addMote(Mote newMote) { + if (newMote != null) { + 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 += EVENT_PIXEL_HEIGHT; + } + if (showLogOutputs) { + h += EVENT_PIXEL_HEIGHT; + } + if (showWatchpoints) { + h += EVENT_PIXEL_HEIGHT; + } + paintedMoteHeight = h; + timelineMoteRuler.revalidate(); + timelineMoteRuler.repaint(); + timeline.revalidate(); + timeline.repaint(); + } + + public void closePlugin() { + simulation.deleteTickObserver(tickObserver); + + /* Remove active mote interface observers */ + for (MoteObservation o: activeMoteObservers) { + o.dispose(); + } + activeMoteObservers.clear(); + } + + public Collection getConfigXML() { + Vector config = new Vector(); + 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); + } + + return config; + } + + public boolean setConfigXML(Collection configXML, boolean visAvailable) { + showRadioRXTX = false; + showRadioChannels = false; + showRadioHW = false; + showLEDs = false; + showLogOutputs = false; + showWatchpoints = false; + + 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; + } + } + + /* 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; + } + + class Timeline extends JPanel { + + private int mouseTimePositionX = -1; + private int mouseTimePositionY = -1; + + private MouseAdapter mouseAdapter = new MouseAdapter() { + public void mouseDragged(MouseEvent e) { + super.mouseDragged(e); + if (mouseTimePositionX >= 0) { + mouseTimePositionX = e.getX(); + mouseTimePositionY = e.getY(); + repaint(); + } + } + public void mousePressed(MouseEvent e) { + if (e.getPoint().getY() < FIRST_MOTE_PIXEL_OFFSET) { + mouseTimePositionX = e.getX(); + mouseTimePositionY = e.getY(); + repaint(); + } + } + public void mouseReleased(MouseEvent e) { + super.mouseReleased(e); + mouseTimePositionX = -1; + repaint(); + } + }; + + public Timeline() { + setToolTipText(""); + setBackground(COLOR_BACKGROUND); + + addMouseListener(mouseAdapter); + addMouseMotionListener(mouseAdapter); + } + + public void paintComponent(Graphics g) { + /*logger.info("Painting timeline: " + startTime + " -> " + (startTime + timeline.getWidth()));*/ + + Rectangle bounds = g.getClipBounds(); + long intervalStart = bounds.x + startTime; + long intervalEnd = intervalStart + bounds.width; + /*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; + for (int mIndex = 0; mIndex < allMoteEvents.size(); mIndex++) { + if (showRadioRXTX) { + RadioRXTXEvent firstEvent = getFirstIntervalEvent(allMoteEvents.get(mIndex).radioRXTXEvents, intervalStart); + if (firstEvent != null) { + firstEvent.paintInterval(g, lineHeightOffset, (int) startTime, intervalEnd); + } + lineHeightOffset += EVENT_PIXEL_HEIGHT; + } + if (showRadioChannels) { + RadioChannelEvent firstEvent = getFirstIntervalEvent(allMoteEvents.get(mIndex).radioChannelEvents, intervalStart); + if (firstEvent != null) { + firstEvent.paintInterval(g, lineHeightOffset, (int) startTime, intervalEnd); + } + lineHeightOffset += EVENT_PIXEL_HEIGHT; + } + if (showRadioHW) { + RadioHWEvent firstEvent = getFirstIntervalEvent(allMoteEvents.get(mIndex).radioHWEvents, intervalStart); + if (firstEvent != null) { + firstEvent.paintInterval(g, lineHeightOffset, (int) startTime, intervalEnd); + } + lineHeightOffset += EVENT_PIXEL_HEIGHT; + } + if (showLEDs) { + LEDEvent firstEvent = getFirstIntervalEvent(allMoteEvents.get(mIndex).ledEvents, intervalStart); + if (firstEvent != null) { + firstEvent.paintInterval(g, lineHeightOffset, (int) startTime, intervalEnd); + } + lineHeightOffset += EVENT_PIXEL_HEIGHT; + } + if (showLogOutputs) { + LogEvent firstEvent = getFirstIntervalEvent(allMoteEvents.get(mIndex).logEvents, intervalStart); + if (firstEvent != null) { + firstEvent.paintInterval(g, lineHeightOffset, (int) startTime, intervalEnd); + } + lineHeightOffset += EVENT_PIXEL_HEIGHT; + } + if (showWatchpoints) { + WatchpointEvent firstEvent = getFirstIntervalEvent(allMoteEvents.get(mIndex).watchpointEvents, intervalStart); + if (firstEvent != null) { + firstEvent.paintInterval(g, lineHeightOffset, (int) startTime, intervalEnd); + } + lineHeightOffset += EVENT_PIXEL_HEIGHT; + } + + lineHeightOffset += EVENT_PIXEL_HEIGHT; + } + + /* Draw vertical time marker (mouse dragged) */ + drawMouseTime(g, intervalStart, intervalEnd); + } + + private T getFirstIntervalEvent(ArrayList 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 millis; + + /* Paint 10ms and 100 ms markers */ + g.setColor(Color.GRAY); + + millis = start - (start % 100); + while (millis <= end) { + if (millis % 100 == 0) { + g.drawLine( + (int)(millis - startTime), (int)0, + (int)(millis - startTime), (int)TIME_MARKER_PIXEL_HEIGHT); + } else { + g.drawLine( + (int)(millis - startTime), (int)0, + (int)(millis - startTime), (int)TIME_MARKER_PIXEL_HEIGHT/2); + } + millis += 10; + } + } + + private void drawMouseTime(Graphics g, long start, long end) { + if (mouseTimePositionX >= 0) { + String str = "Time: " + (mouseTimePositionX + startTime); + int h = g.getFontMetrics().getHeight(); + int w = g.getFontMetrics().stringWidth(str) + 6; + int y=mouseTimePositionY end?w:0; /* Don't write outside visible area */ + + /* Line */ + g.setColor(Color.GRAY); + g.drawLine( + mouseTimePositionX, 0, + mouseTimePositionX, getHeight()); + + /* Text box */ + g.setColor(Color.DARK_GRAY); + g.fillRect( + mouseTimePositionX-delta, y, + w, h); + g.setColor(Color.BLACK); + g.drawRect( + mouseTimePositionX-delta, y, + w, h); + g.setColor(Color.WHITE); + g.drawString(str, + mouseTimePositionX+3-delta, + y+h-1); + } + } + + public int getWidth() { + return (int) (simulation.getSimulationTime() - startTime); + } + + public String getToolTipText(MouseEvent event) { + if (event.getPoint().y <= FIRST_MOTE_PIXEL_OFFSET) { + return "Click to display time"; + } + + /* Mote */ + int mote = (event.getPoint().y-FIRST_MOTE_PIXEL_OFFSET)/paintedMoteHeight; + if (mote < 0 || mote >= allMoteEvents.size()) { + return null; + } + String tooltip = "Mote: " + allMoteEvents.get(mote).mote + "
"; + + /* Time */ + long time = event.getPoint().x + startTime; + tooltip += "Time: " + time + "
"; + + /* Event */ + ArrayList 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 + "
"; + } + } + + tooltip += ""; + return tooltip; + } + } + + class MoteRuler extends JPanel { + public MoteRuler() { + setPreferredSize(new Dimension(35, 1)); + setToolTipText(""); + 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 = "??"; + if (moteLog.mote.getInterfaces().getMoteID() != null) { + str = "" + moteLog.mote.getInterfaces().getMoteID().getMoteID(); + } + 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 "" + m + "
Click mote for options"; + } + } + + /* Event classes */ + abstract class MoteEvent { + long time; + public MoteEvent(long time) { + this.time = time; + } + } + class RadioRXTXEvent extends MoteEvent { + RadioRXTXEvent prev = null; + RadioRXTXEvent next = null; + RadioEvent state = null; + public RadioRXTXEvent(long time, RadioEvent ev) { + super(time); + this.state = ev; + } + public void paintInterval(Graphics g, int lineHeightOffset, int startTime, long end) { + RadioRXTXEvent ev = this; + while (ev != null && ev.time < end) { + int w; + + /* Paint until next event or end of clip */ + if (ev.next != null) { + w = (int) (ev.next.time - ev.time); + } else { + w = (int) (end - ev.time); /* No more events */ + } + + /* Ignore painting events with zero width */ + if (w == 0) { + ev = ev.next; + continue; + } + + if (ev.state == RadioEvent.TRANSMISSION_STARTED) { + g.setColor(Color.BLUE); + } else if (ev.state == RadioEvent.RECEPTION_STARTED) { + g.setColor(Color.GREEN); + } else if (ev.state == RadioEvent.RECEPTION_INTERFERED) { + g.setColor(Color.RED); + } else { + /*g.setColor(Color.LIGHT_GRAY);*/ + ev = ev.next; + continue; + } + + g.fillRect( + (int)(ev.time - startTime), lineHeightOffset, + w, EVENT_PIXEL_HEIGHT + ); + + ev = ev.next; + } + } + public String toString() { + if (state == RadioEvent.TRANSMISSION_STARTED) { + return "Radio TX started at " + time + "
"; + } else if (state == RadioEvent.TRANSMISSION_FINISHED) { + return "Radio TX finished at " + time + "
"; + } else if (state == RadioEvent.RECEPTION_STARTED) { + return "Radio RX started at " + time + "
"; + } else if (state == RadioEvent.RECEPTION_FINISHED) { + return "Radio RX finished at " + time + "
"; + } else if (state == RadioEvent.RECEPTION_INTERFERED) { + return "Radio reception was interfered at " + time + "
"; + } + return "Unknown event
"; + } + } + class RadioChannelEvent extends MoteEvent { + RadioChannelEvent prev = null; + RadioChannelEvent next = null; + public RadioChannelEvent(long time) { + super(time); + } + public void paintInterval(Graphics g, int lineHeightOffset, int startTime, long end) { + RadioChannelEvent ev = this; + while (ev != null && ev.time < end) { + int w; + + /* Paint until next event or end of clip */ + if (ev.next != null) { + w = (int) (ev.next.time - ev.time); + } else { + w = (int) (end - ev.time); /* No more events */ + } + + /* Ignore painting events with zero width */ + if (w == 0) { + ev = ev.next; + continue; + } + + g.setColor(Color.GRAY); + g.fillRect( + (int)(ev.time - startTime), lineHeightOffset, + w, EVENT_PIXEL_HEIGHT + ); + + ev = ev.next; + } + } + } + class RadioHWEvent extends MoteEvent { + RadioHWEvent prev = null; + RadioHWEvent next = null; + boolean on; + public RadioHWEvent(long time, boolean on) { + super(time); + this.on = on; + } + public void paintInterval(Graphics g, int lineHeightOffset, int startTime, long end) { + RadioHWEvent ev = this; + while (ev != null && ev.time < end) { + int w; + + /* Paint until next event or end of clip */ + if (ev.next != null) { + w = (int) (ev.next.time - ev.time); + } else { + w = (int) (end - ev.time); /* No more events */ + } + + /* Ignore painting events with zero width */ + if (w == 0) { + ev = ev.next; + continue; + } + + if (!ev.on) { + ev = ev.next; + continue; + } + g.setColor(Color.GRAY); + g.fillRect( + (int)(ev.time - startTime), lineHeightOffset, + w, EVENT_PIXEL_HEIGHT + ); + + ev = ev.next; + } + } + public String toString() { + return "Radio HW was turned " + (on?"on":"off") + " at time " + time + "
"; + } + } + class LEDEvent extends MoteEvent { + LEDEvent prev = null; + LEDEvent next = null; + 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); + prev = null; + next = null; + } + public void paintInterval(Graphics g, int lineHeightOffset, int startTime, long end) { + LEDEvent ev = this; + while (ev != null && ev.time < end) { + int w; + + /* Paint until next event or end of clip */ + if (ev.next != null) { + w = (int) (ev.next.time - ev.time); + } else { + w = (int) (end - ev.time); /* No more events */ + } + + /* Ignore painting events with zero width */ + if (w == 0) { + ev = ev.next; + continue; + } + + if (!ev.red && !ev.green && !ev.blue) { + g.setColor(Color.WHITE); + } else if (ev.red && ev.green && ev.blue) { + g.setColor(Color.LIGHT_GRAY); + } else { + g.setColor(ev.color); + } + g.fillRect( + (int)(ev.time - startTime), lineHeightOffset, + w, EVENT_PIXEL_HEIGHT + ); + + ev = ev.next; + } + } + public String toString() { + return + "LED state:
" + + "Red = " + (red?"ON":"OFF") + "
" + + "Green = " + (green?"ON":"OFF") + "
" + + "Blue = " + (blue?"ON":"OFF") + "
"; + } + } + class LogEvent extends MoteEvent { + LogEvent prev = null; + LogEvent next = null; + public LogEvent(long time) { + super(time); + } + public void paintInterval(Graphics g, int lineHeightOffset, int startTime, long end) { + LogEvent ev = this; + while (ev != null && ev.time < end) { + int w; + + /* Paint until next event or end of clip */ + if (ev.next != null) { + w = (int) (ev.next.time - ev.time); + } else { + w = (int) (end - ev.time); /* No more events */ + } + + /* Ignore painting events with zero width */ + if (w == 0) { + ev = ev.next; + continue; + } + + g.setColor(Color.GREEN); + g.fillRect( + (int)(ev.time - startTime), lineHeightOffset, + w, EVENT_PIXEL_HEIGHT + ); + + ev = ev.next; + } + } + + } + class WatchpointEvent extends MoteEvent { + WatchpointEvent prev = null; + WatchpointEvent next = null; + public WatchpointEvent(long time) { + super(time); + } + public void paintInterval(Graphics g, int lineHeightOffset, int startTime, long end) { + WatchpointEvent ev = this; + while (ev != null && ev.time < end) { + int w; + + /* Paint until next event or end of clip */ + if (ev.next != null) { + w = (int) (ev.next.time - ev.time); + } else { + w = (int) (end - ev.time); /* No more events */ + } + + /* Ignore painting events with zero width */ + if (w == 0) { + ev = ev.next; + continue; + } + + g.setColor(Color.BLUE); + g.fillRect( + (int)(ev.time - startTime), lineHeightOffset, + w, EVENT_PIXEL_HEIGHT + ); + + ev = ev.next; + } + } + + } + class MoteEvents { + Mote mote; + ArrayList radioRXTXEvents; + ArrayList radioChannelEvents; + ArrayList radioHWEvents; + ArrayList ledEvents; + ArrayList logEvents; + ArrayList watchpointEvents; + + private RadioRXTXEvent lastRadioRXTXEvent = null; + private RadioChannelEvent lastRadioChannelEvent = null; + private RadioHWEvent lastRadioHWEvent = null; + private LEDEvent lastLEDEvent = null; + private LogEvent lastLogEvent = null; + private WatchpointEvent lastWatchpointEvent = null; + + public MoteEvents(Mote mote) { + this.mote = mote; + this.radioRXTXEvents = new ArrayList(); + this.radioChannelEvents = new ArrayList(); + this.radioHWEvents = new ArrayList(); + this.ledEvents = new ArrayList(); + this.logEvents = new ArrayList(); + this.watchpointEvents = new ArrayList(); + } + + 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); + } + } + +}