();
+
+ 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)