diff --git a/tools/cooja/apps/powertracker/build.xml b/tools/cooja/apps/powertracker/build.xml new file mode 100644 index 000000000..cd608277f --- /dev/null +++ b/tools/cooja/apps/powertracker/build.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/cooja/apps/powertracker/cooja.config b/tools/cooja/apps/powertracker/cooja.config new file mode 100644 index 000000000..1df154e2b --- /dev/null +++ b/tools/cooja/apps/powertracker/cooja.config @@ -0,0 +1,2 @@ +se.sics.cooja.GUI.PLUGINS = + PowerTracker +se.sics.cooja.GUI.JARFILES = + powertracker.jar diff --git a/tools/cooja/apps/powertracker/java/PowerTracker.java b/tools/cooja/apps/powertracker/java/PowerTracker.java new file mode 100644 index 000000000..0601cb32e --- /dev/null +++ b/tools/cooja/apps/powertracker/java/PowerTracker.java @@ -0,0 +1,524 @@ +/* + * Copyright (c) 2012, 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. + */ + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Observable; +import java.util.Observer; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.Timer; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.DefaultTableCellRenderer; + +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.SimEventCentral.MoteCountListener; +import se.sics.cooja.Simulation; +import se.sics.cooja.VisPlugin; +import se.sics.cooja.interfaces.Radio; + +/** + * Tracks radio events to sum up transmission, reception, and radio on times. + * This plugin can be run without visualization, i.e. from a Contiki test. + * + * @author Fredrik Osterlind, Adam Dunkels + */ +@ClassDescription("PowerTracker") +@PluginType(PluginType.SIM_PLUGIN) +public class PowerTracker extends VisPlugin { + private static Logger logger = Logger.getLogger(PowerTracker.class); + + private static final int POWERTRACKER_UPDATE_INTERVAL = 100; /* ms */ + + private static final int COLUMN_MOTE = 0; + private static final int COLUMN_RADIOON = 1; + private static final int COLUMN_RADIOTX = 2; + private static final int COLUMN_RADIORX = 3; + + private Simulation simulation; + private MoteCountListener moteCountListener; + private ArrayList moteTrackers = new ArrayList(); + + private JTable table; + private int tableMaxRadioOnIndex = -1; + + public PowerTracker(final Simulation simulation, final GUI gui) { + super("PowerTracker", gui, false); + this.simulation = simulation; + + /* Automatically add/delete motes */ + simulation.getEventCentral().addMoteCountListener(moteCountListener = new MoteCountListener() { + public void moteWasAdded(Mote mote) { + addMote(mote); + table.invalidate(); + table.repaint(); + } + public void moteWasRemoved(Mote mote) { + removeMote(mote); + table.invalidate(); + table.repaint(); + } + }); + for (Mote m: simulation.getMotes()) { + addMote(m); + } + + if (!GUI.isVisualized()) { + return; + } + + AbstractTableModel model = new AbstractTableModel() { + public int getRowCount() { + return moteTrackers.size()+1; + } + public int getColumnCount() { + return 4; + } + public String getColumnName(int col) { + if (col == COLUMN_MOTE) { + return "Mote"; + } + if (col == COLUMN_RADIOON) { + return "Radio on (%)"; + } + if (col == COLUMN_RADIOTX) { + return "Radio TX (%)"; + } + if (col == COLUMN_RADIORX) { + return "Radio RX (%)"; + } + return null; + } + public Object getValueAt(int rowIndex, int col) { + if (rowIndex < 0 || rowIndex >= moteTrackers.size()+1) { + return null; + } + + if (rowIndex == moteTrackers.size()) { + /* Average */ + long radioOn = 0; + long radioTx = 0; + long radioRx = 0; + long duration = 0; + for (MoteTracker mt: moteTrackers) { + radioOn += mt.radioOn; + radioTx += mt.radioTx; + radioRx += mt.radioRx; + + duration += mt.duration; + } + + if (col == COLUMN_MOTE) { + return "AVERAGE"; + } + if (col == COLUMN_RADIOON) { + return String.format("%2.2f%%", 100.0*radioOn/duration); + } + if (col == COLUMN_RADIOTX) { + return String.format("%2.2f%%", 100.0*radioTx/duration); + } + if (col == COLUMN_RADIORX) { + return String.format("%2.2f%%", 100.0*radioRx/duration); + } + return null; + } + + MoteTracker rule = moteTrackers.get(rowIndex); + if (col == COLUMN_MOTE) { + return rule.mote.toString(); + } + if (col == COLUMN_RADIOON) { + return String.format("%2.2f%%", 100.0*rule.getRadioOnRatio()); + } + if (col == COLUMN_RADIOTX) { + return String.format("%2.2f%%", 100.0*rule.getRadioTxRatio()); + } + if (col == COLUMN_RADIORX) { + return String.format("%2.2f%%", 100.0*rule.getRadioRxRatio()); + } + return null; + } + }; + table = new JTable(model) { + public String getToolTipText(MouseEvent e) { + java.awt.Point p = e.getPoint(); + int rowIndex = table.rowAtPoint(p); + if (rowIndex < 0 || rowIndex >= moteTrackers.size()) { + return null; + } + MoteTracker mt = moteTrackers.get(rowIndex); + return "
" + mt.toString() + "";
+      }
+    };
+    table.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {
+      public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
+        Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+        if (row == tableMaxRadioOnIndex) {
+          setBackground(isSelected ? table.getSelectionBackground():Color.RED);
+          setForeground(isSelected ? table.getSelectionForeground():Color.WHITE);
+        } else {
+          setBackground(isSelected ? table.getSelectionBackground():table.getBackground());
+          setForeground(isSelected ? table.getSelectionForeground():table.getForeground());
+        }
+        return c;
+      }
+    });
+
+    Box control = Box.createHorizontalBox();
+    control.add(Box.createHorizontalGlue());
+    control.add(new JButton(printAction));
+    control.add(new JButton(resetAction));
+
+    this.getContentPane().add(BorderLayout.CENTER, new JScrollPane(table));
+    this.getContentPane().add(BorderLayout.SOUTH, control);
+    setSize(400, 400);
+
+    repaintTimer.start();
+  }
+
+  private Action resetAction = new AbstractAction("Reset") {
+    public void actionPerformed(ActionEvent e) {
+      Runnable r = new Runnable() {
+        public void run() {
+          reset();
+        }
+      };
+      if (simulation.isRunning()) {
+        simulation.invokeSimulationThread(r);
+      } else {
+        r.run();
+      }
+    }
+  };
+
+  private Action printAction = new AbstractAction("Print to console/Copy to clipboard") {
+    public void actionPerformed(ActionEvent e) {
+      String output = radioStatistics(true, true, false);
+      logger.info("PowerTracker output:\n\n" + output);
+
+      Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+      StringSelection stringSelection = new StringSelection(output);
+      clipboard.setContents(stringSelection, null);
+    }
+  };
+
+  public String radioStatistics() {
+    return radioStatistics(true, true, false);
+  }
+
+  public String radioStatistics(boolean radioHW, boolean radioRXTX, boolean onlyAverage) {
+    StringBuilder sb = new StringBuilder();
+
+    /* Average */
+    long radioOn = 0;
+    long radioTx = 0;
+    long radioRx = 0;
+    long radioInterfered = 0;
+    long duration = 0;
+    for (MoteTracker mt: moteTrackers) {
+      radioOn += mt.radioOn;
+      radioTx += mt.radioTx;
+      radioRx += mt.radioRx;
+      radioInterfered += mt.radioInterfered;
+
+      duration += mt.duration;
+    }
+    if (radioHW) {
+      sb.append(String.format("AVG" + " ON " + (radioOn + " us ") + "%2.2f %%", 100.0*radioOn/duration) + "\n");
+    }
+    if (radioRXTX) {
+      sb.append(String.format("AVG" + " TX " + (radioTx + " us ") + "%2.2f %%", 100.0*radioTx/duration) + "\n");
+      sb.append(String.format("AVG" + " RX " + (radioRx + " us ") + "%2.2f %%", 100.0*radioRx/duration) + "\n");
+      sb.append(String.format("AVG" + " INT " + (radioInterfered + " us ") + "%2.2f %%", 100.0*radioInterfered/duration) + "\n");
+    }
+
+    if (onlyAverage) {
+      return sb.toString();
+    }
+
+    for (MoteTracker mt: moteTrackers) {
+      sb.append(mt.toString(radioHW, radioRXTX));
+    }
+    return sb.toString();
+  }
+
+  private static class MoteTracker implements Observer {
+    /* last radio state */
+    private boolean radioWasOn;
+    private RadioState lastRadioState;
+    private long lastUpdateTime;
+
+    /* accumulating radio state durations */
+    long duration = 0;
+    long radioOn = 0;
+    long radioTx = 0;
+    long radioRx = 0;
+    long radioInterfered = 0;
+
+    private Simulation simulation;
+    private Mote mote;
+    private Radio radio;
+
+    public MoteTracker(Mote mote) {
+      this.simulation = mote.getSimulation();
+      this.mote = mote;
+      this.radio = mote.getInterfaces().getRadio();
+
+      radioWasOn = radio.isReceiverOn();
+      if (radio.isTransmitting()) {
+        lastRadioState = RadioState.TRANSMITTING;
+      } else if (radio.isReceiving()) {
+        lastRadioState = RadioState.RECEIVING;
+      } else if (radio.isInterfered()){
+        lastRadioState = RadioState.INTERFERED;
+      } else {
+        lastRadioState = RadioState.IDLE;
+      }
+      lastUpdateTime = simulation.getSimulationTime();
+
+      radio.addObserver(this);
+    }
+
+    public void update(Observable o, Object arg) {
+      update();
+    }
+    public void update() {
+      long now = simulation.getSimulationTime();
+
+      accumulateDuration(now - lastUpdateTime);
+
+      /* Radio on/off */
+      if (radioWasOn) {
+        accumulateRadioOn(now - lastUpdateTime);
+      }
+
+      /* Radio tx/rx */
+      if (lastRadioState == RadioState.TRANSMITTING) {
+        accumulateRadioTx(now - lastUpdateTime);
+      } else if (lastRadioState == RadioState.RECEIVING) {
+        accumulateRadioRx(now - lastUpdateTime);
+      } else if (lastRadioState == RadioState.INTERFERED) {
+        accumulateRadioIntefered(now - lastUpdateTime);
+      }
+
+      /* Await next radio event */
+      if (radio.isTransmitting()) {
+        lastRadioState = RadioState.TRANSMITTING;
+      } else if (!radio.isReceiverOn()) {
+        lastRadioState = RadioState.IDLE;
+      } else if (radio.isInterfered()) {
+        lastRadioState = RadioState.INTERFERED;
+      } else if (radio.isReceiving()) {
+        lastRadioState = RadioState.RECEIVING;
+      } else {
+        lastRadioState = RadioState.IDLE;
+      }
+      radioWasOn = radio.isReceiverOn();
+      lastUpdateTime = now;
+    }
+
+    protected void accumulateDuration(long t) {
+      duration += t;
+    }
+    protected void accumulateRadioOn(long t) {
+      radioOn += t;
+    }
+    protected void accumulateRadioTx(long t) {
+      radioTx += t;
+    }
+    protected void accumulateRadioRx(long t) {
+      radioRx += t;
+    }
+    protected void accumulateRadioIntefered(long t) {
+      radioInterfered += t;
+    }
+
+    protected double getRadioOnRatio() {
+      return 1.0*radioOn/duration;
+    }
+
+    protected double getRadioTxRatio() {
+      return 1.0*radioTx/duration;
+    }
+
+    protected double getRadioInterferedRatio() {
+      return 1.0*radioInterfered/duration;
+    }
+
+    protected double getRadioRxRatio() {
+      return 1.0*radioRx/duration;
+    }
+
+    public Mote getMote() {
+      return mote;
+    }
+
+    public void dispose() {
+      radio.deleteObserver(this);
+      radio = null;
+      mote = null;
+    }
+
+    public String toString() {
+      return toString(true, true);
+    }
+    public String toString(boolean radioHW, boolean radioRXTX) {
+      StringBuilder sb = new StringBuilder();
+      String moteString = mote.toString().replace(' ', '_');
+
+      sb.append(moteString + " MONITORED " + duration + " us\n");
+      if (radioHW) {
+        sb.append(String.format(moteString + " ON " + (radioOn + " us ") + "%2.2f %%", 100.0*getRadioOnRatio()) + "\n");
+      }
+      if (radioRXTX) {
+        sb.append(String.format(moteString + " TX " + (radioTx + " us ") + "%2.2f %%", 100.0*getRadioTxRatio()) + "\n");
+        sb.append(String.format(moteString + " RX " + (radioRx + " us ") + "%2.2f %%", 100.0*getRadioRxRatio()) + "\n");
+        sb.append(String.format(moteString + " INT " + (radioInterfered + " us ") + "%2.2f %%", 100.0*getRadioInterferedRatio()) + "\n");
+      }
+      return sb.toString();
+    }
+  }
+
+  private MoteTracker createMoteTracker(Mote mote) {
+    final Radio moteRadio = mote.getInterfaces().getRadio();
+    if (moteRadio == null) {
+      return null;
+    }
+
+    /* Radio observer */
+    MoteTracker tracker = new MoteTracker(mote);
+    tracker.update(null, null);
+    return tracker;
+  }
+
+  public void reset() {
+    while (moteTrackers.size() > 0) {
+      removeMote(moteTrackers.get(0).mote);
+    }
+    for (Mote m: simulation.getMotes()) {
+      addMote(m);
+    }
+  }
+
+  private void addMote(Mote mote) {
+    if (mote == null) {
+      return;
+    }
+
+    MoteTracker t = createMoteTracker(mote);
+    if (t != null) {
+      moteTrackers.add(t);
+    }
+
+    setTitle("PowerTracker: " + moteTrackers.size() + " motes");
+  }
+
+  private void removeMote(Mote mote) {
+    /* Remove mote tracker(s) */
+    MoteTracker[] trackers = moteTrackers.toArray(new MoteTracker[0]);
+    for (MoteTracker t: trackers) {
+      if (t.getMote() == mote) {
+        t.dispose();
+        moteTrackers.remove(t);
+      }
+    }
+
+    setTitle("PowerTracker: " + moteTrackers.size() + " motes");
+  }
+
+  public void closePlugin() {
+    /* Remove repaint timer */
+    repaintTimer.stop();
+
+    simulation.getEventCentral().removeMoteCountListener(moteCountListener);
+
+    /* Remove mote trackers */
+    for (Mote m: simulation.getMotes()) {
+      removeMote(m);
+    }
+    if (!moteTrackers.isEmpty()) {
+      logger.fatal("Mote observers not cleaned up correctly");
+      for (MoteTracker t: moteTrackers.toArray(new MoteTracker[0])) {
+        t.dispose();
+      }
+    }
+  }
+
+  public enum RadioState {
+    IDLE, RECEIVING, TRANSMITTING, INTERFERED
+  }
+
+  private Timer repaintTimer = new Timer(POWERTRACKER_UPDATE_INTERVAL, new ActionListener() {
+    public void actionPerformed(ActionEvent e) {
+      /* Identify max radio on */
+      double maxRadioOn = 0;
+      int maxRadioOnIndex = -1;
+      for (int i=0; i < moteTrackers.size(); i++) {
+        MoteTracker mt = moteTrackers.get(i);
+        if (mt.getRadioOnRatio() > maxRadioOn) {
+          maxRadioOn = mt.getRadioOnRatio();
+          maxRadioOnIndex = i;
+        }
+      }
+      if (maxRadioOnIndex >= 0) {
+        tableMaxRadioOnIndex = maxRadioOnIndex;
+      }
+
+      table.repaint();
+    }
+  });
+
+  public Collection getConfigXML() {
+    return null;
+  }
+  public boolean setConfigXML(Collection configXML, boolean visAvailable) {
+    return true;
+  }
+
+}
diff --git a/tools/cooja/config/external_tools_freebsd.config b/tools/cooja/config/external_tools_freebsd.config
index b0564ec31..333b16cd4 100644
--- a/tools/cooja/config/external_tools_freebsd.config
+++ b/tools/cooja/config/external_tools_freebsd.config
@@ -22,7 +22,7 @@ AR_COMMAND_2 =
 CONTIKI_STANDARD_PROCESSES = sensors_process;etimer_process
 CORECOMM_TEMPLATE_FILENAME = corecomm_template.java
 PATH_JAVAC = javac
-DEFAULT_PROJECTDIRS = [CONTIKI_DIR]/tools/cooja/apps/mrm;[CONTIKI_DIR]/tools/cooja/apps/mspsim;[CONTIKI_DIR]/tools/cooja/apps/avrora;[CONTIKI_DIR]/tools/cooja/apps/serial_socket;[CONTIKI_DIR]/tools/cooja/apps/collect-view
+DEFAULT_PROJECTDIRS = [CONTIKI_DIR]/tools/cooja/apps/mrm;[CONTIKI_DIR]/tools/cooja/apps/mspsim;[CONTIKI_DIR]/tools/cooja/apps/avrora;[CONTIKI_DIR]/tools/cooja/apps/serial_socket;[CONTIKI_DIR]/tools/cooja/apps/collect-view;[CONTIKI_DIR]/tools/cooja/apps/powertracker
 
 PARSE_WITH_COMMAND=false
 PARSE_COMMAND=nm -a $(LIBFILE)
diff --git a/tools/cooja/config/external_tools_linux.config b/tools/cooja/config/external_tools_linux.config
index 8e433b543..8c4d8826b 100644
--- a/tools/cooja/config/external_tools_linux.config
+++ b/tools/cooja/config/external_tools_linux.config
@@ -22,7 +22,7 @@ AR_COMMAND_2 =
 CONTIKI_STANDARD_PROCESSES = sensors_process;etimer_process
 CORECOMM_TEMPLATE_FILENAME = corecomm_template.java
 PATH_JAVAC = javac
-DEFAULT_PROJECTDIRS = [CONTIKI_DIR]/tools/cooja/apps/mrm;[CONTIKI_DIR]/tools/cooja/apps/mspsim;[CONTIKI_DIR]/tools/cooja/apps/avrora;[CONTIKI_DIR]/tools/cooja/apps/serial_socket;[CONTIKI_DIR]/tools/cooja/apps/collect-view
+DEFAULT_PROJECTDIRS = [CONTIKI_DIR]/tools/cooja/apps/mrm;[CONTIKI_DIR]/tools/cooja/apps/mspsim;[CONTIKI_DIR]/tools/cooja/apps/avrora;[CONTIKI_DIR]/tools/cooja/apps/serial_socket;[CONTIKI_DIR]/tools/cooja/apps/collect-view;[CONTIKI_DIR]/tools/cooja/apps/powertracker
 
 PARSE_WITH_COMMAND=false
 PARSE_COMMAND=nm -a $(LIBFILE)
diff --git a/tools/cooja/config/external_tools_linux_64.config b/tools/cooja/config/external_tools_linux_64.config
index 7609ca905..2d238b504 100644
--- a/tools/cooja/config/external_tools_linux_64.config
+++ b/tools/cooja/config/external_tools_linux_64.config
@@ -22,7 +22,7 @@ AR_COMMAND_2 =
 CONTIKI_STANDARD_PROCESSES = sensors_process;etimer_process
 CORECOMM_TEMPLATE_FILENAME = corecomm_template.java
 PATH_JAVAC = javac
-DEFAULT_PROJECTDIRS = [CONTIKI_DIR]/tools/cooja/apps/mrm;[CONTIKI_DIR]/tools/cooja/apps/mspsim;[CONTIKI_DIR]/tools/cooja/apps/avrora;[CONTIKI_DIR]/tools/cooja/apps/serial_socket;[CONTIKI_DIR]/tools/cooja/apps/collect-view
+DEFAULT_PROJECTDIRS = [CONTIKI_DIR]/tools/cooja/apps/mrm;[CONTIKI_DIR]/tools/cooja/apps/mspsim;[CONTIKI_DIR]/tools/cooja/apps/avrora;[CONTIKI_DIR]/tools/cooja/apps/serial_socket;[CONTIKI_DIR]/tools/cooja/apps/collect-view;[CONTIKI_DIR]/tools/cooja/apps/powertracker
 
 PARSE_WITH_COMMAND=false
 PARSE_COMMAND=nm -a $(LIBFILE)
diff --git a/tools/cooja/config/external_tools_macosx.config b/tools/cooja/config/external_tools_macosx.config
index f15ba75da..45efce1d1 100644
--- a/tools/cooja/config/external_tools_macosx.config
+++ b/tools/cooja/config/external_tools_macosx.config
@@ -22,7 +22,7 @@ AR_COMMAND_2 =
 CONTIKI_STANDARD_PROCESSES = sensors_process;etimer_process
 CORECOMM_TEMPLATE_FILENAME = corecomm_template.java
 PATH_JAVAC = javac
-DEFAULT_PROJECTDIRS = [CONTIKI_DIR]/tools/cooja/apps/mrm;[CONTIKI_DIR]/tools/cooja/apps/mspsim;[CONTIKI_DIR]/tools/cooja/apps/avrora;[CONTIKI_DIR]/tools/cooja/apps/serial_socket;[CONTIKI_DIR]/tools/cooja/apps/collect-view
+DEFAULT_PROJECTDIRS = [CONTIKI_DIR]/tools/cooja/apps/mrm;[CONTIKI_DIR]/tools/cooja/apps/mspsim;[CONTIKI_DIR]/tools/cooja/apps/avrora;[CONTIKI_DIR]/tools/cooja/apps/serial_socket;[CONTIKI_DIR]/tools/cooja/apps/collect-view;[CONTIKI_DIR]/tools/cooja/apps/powertracker
 
 PARSE_WITH_COMMAND = true
 PARSE_COMMAND = /opt/contiki-2.x/tools/cooja/examples/jni_test/mac_users/nmandsize $(LIBFILE)
diff --git a/tools/cooja/config/external_tools_win32.config b/tools/cooja/config/external_tools_win32.config
index c35ab48cb..012750a63 100644
--- a/tools/cooja/config/external_tools_win32.config
+++ b/tools/cooja/config/external_tools_win32.config
@@ -22,7 +22,7 @@ AR_COMMAND_2 =
 CONTIKI_STANDARD_PROCESSES = sensors_process;etimer_process
 CORECOMM_TEMPLATE_FILENAME = corecomm_template.java
 PATH_JAVAC = javac
-DEFAULT_PROJECTDIRS = [CONTIKI_DIR]/tools/cooja/apps/mrm;[CONTIKI_DIR]/tools/cooja/apps/mspsim;[CONTIKI_DIR]/tools/cooja/apps/avrora;[CONTIKI_DIR]/tools/cooja/apps/serial_socket;[CONTIKI_DIR]/tools/cooja/apps/collect-view
+DEFAULT_PROJECTDIRS = [CONTIKI_DIR]/tools/cooja/apps/mrm;[CONTIKI_DIR]/tools/cooja/apps/mspsim;[CONTIKI_DIR]/tools/cooja/apps/avrora;[CONTIKI_DIR]/tools/cooja/apps/serial_socket;[CONTIKI_DIR]/tools/cooja/apps/collect-view;[CONTIKI_DIR]/tools/cooja/apps/powertracker
 
 PARSE_WITH_COMMAND = true
 PARSE_COMMAND=nm -n -C $(LIBFILE)