diff --git a/tools/cooja/config/cooja_default.config b/tools/cooja/config/cooja_default.config index 15ab4b310..c76e29a45 100644 --- a/tools/cooja/config/cooja_default.config +++ b/tools/cooja/config/cooja_default.config @@ -29,9 +29,10 @@ se.sics.cooja.contikimote.interfaces.ContikiCFS.CONSUMPTION_PER_READ_CHAR_mQ = 1 se.sics.cooja.contikimote.ContikiMoteType.MOTE_INTERFACES = se.sics.cooja.interfaces.Position se.sics.cooja.interfaces.Battery se.sics.cooja.contikimote.interfaces.ContikiVib se.sics.cooja.contikimote.interfaces.ContikiMoteID se.sics.cooja.contikimote.interfaces.ContikiRS232 se.sics.cooja.contikimote.interfaces.ContikiBeeper se.sics.cooja.contikimote.interfaces.ContikiIPAddress se.sics.cooja.contikimote.interfaces.ContikiRadio se.sics.cooja.contikimote.interfaces.ContikiButton se.sics.cooja.contikimote.interfaces.ContikiPIR se.sics.cooja.contikimote.interfaces.ContikiClock se.sics.cooja.contikimote.interfaces.ContikiLED se.sics.cooja.contikimote.interfaces.ContikiLog se.sics.cooja.contikimote.interfaces.ContikiCFS se.sics.cooja.contikimote.ContikiMoteType.C_SOURCES = -se.sics.cooja.GUI.MOTETYPES = se.sics.cooja.contikimote.ContikiMoteType se.sics.cooja.motes.DummyMoteType +se.sics.cooja.GUI.MOTETYPES = se.sics.cooja.contikimote.ContikiMoteType se.sics.cooja.motes.DummyMoteType se.sics.cooja.mantismote.MantisMoteType se.sics.cooja.GUI.PLUGINS = se.sics.cooja.plugins.VisState se.sics.cooja.plugins.VisBattery se.sics.cooja.plugins.VisTraffic se.sics.cooja.plugins.LogListener se.sics.cooja.plugins.MoteInformation se.sics.cooja.plugins.MoteInterfaceViewer se.sics.cooja.plugins.VariableWatcher se.sics.cooja.GUI.IP_DISTRIBUTORS = se.sics.cooja.ipdistributors.RandomIPDistributor se.sics.cooja.ipdistributors.SpatialIPDistributor se.sics.cooja.ipdistributors.IdIPDistributor se.sics.cooja.GUI.POSITIONERS = se.sics.cooja.positioners.RandomPositioner se.sics.cooja.positioners.LinearPositioner se.sics.cooja.positioners.EllipsePositioner se.sics.cooja.GUI.RADIOMEDIUMS = se.sics.cooja.radiomediums.StandardRadioMedium se.sics.cooja.radiomediums.SilentRadioMedium +se.sics.cooja.mantismote.MantisMoteType.MOTE_INTERFACES = se.sics.cooja.interfaces.Position diff --git a/tools/cooja/config/mantis_template.c b/tools/cooja/config/mantis_template.c new file mode 100644 index 000000000..e4c0ac73a --- /dev/null +++ b/tools/cooja/config/mantis_template.c @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2006, 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: mantis_template.c,v 1.1 2006/11/09 19:32:53 fros4943 Exp $ + */ + +/** + * \file + * C code template for generating Mantis source code files from COOJA + * Simulator. This file should not be compiled directly. + * \author + * Fredrik Osterlind + */ + +#include +#include +#include +#include +#include "mos.h" +#include "sem.h" +#include "msched.h" + +extern int main(); /* in mantis/src/mos/sys/main.c */ +extern uint32_t cooja_time; /* in mantis/src/mos/sys/main.c */ +extern pthread_cond_t cooja_cv; /* in mantis/src/mos/sys/main.c */ +extern mos_thread_t threads[MAX_THREADS]; /* in mantis/src/mos/sys/main.c */ + +/* + * referenceVar is used for comparing absolute and process relative memory. + * (this must not be static due to memory locations) + */ +int referenceVar; + +int dataVar = 1; +int i; + +/*---------------------------------------------------------------------------*/ +/** + * \brief Initialize a mote by starting processes etc. + * + * This function initializes a mote by starting certain + * processes and setting up the environment. + * + * This is a JNI function and should only be called via the + * responsible Java part (MoteType.java). + */ +JNIEXPORT void JNICALL +Java_se_sics_cooja_corecomm_[CLASS_NAME]_init(JNIEnv *env, jobject obj) +{ + main(); +} +/*---------------------------------------------------------------------------*/ +/** + * \brief Get a segment from the process memory. + * \param start Start address of segment + * \param length Size of memory segment + * \return Java byte array containing a copy of memory segment. + * + * Fetches a memory segment from the process memory starting at + * (start), with size (length). This function does not perform + * ANY error checking, and the process may crash if addresses are + * not available/readable. + * + * This is a JNI function and should only be called via the + * responsible Java part (MoteType.java). + */ +JNIEXPORT void JNICALL +Java_se_sics_cooja_corecomm_[CLASS_NAME]_getMemory(JNIEnv *env, jobject obj, jint start, jint length, jbyteArray mem_arr) +{ + (*env)->SetByteArrayRegion(env, mem_arr, 0, (size_t) length, (jbyte *) start); +} +/*---------------------------------------------------------------------------*/ +/** + * \brief Replace a segment of the process memory with given byte array. + * \param start Start address of segment + * \param length Size of memory segment + * \param mem_arr Byte array contaning new memory + * + * Replaces a process memory segment with given byte array. + * This function does not perform ANY error checking, and the + * process may crash if addresses are not available/writable. + * + * This is a JNI function and should only be called via the + * responsible Java part (MoteType.java). + */ +JNIEXPORT void JNICALL +Java_se_sics_cooja_corecomm_[CLASS_NAME]_setMemory(JNIEnv *env, jobject obj, jint start, jint length, jbyteArray mem_arr) +{ + jbyte *mem = (*env)->GetByteArrayElements(env, mem_arr, 0); + memcpy((void *) start, mem, length); + (*env)->ReleaseByteArrayElements(env, mem_arr, mem, 0); +} +/*---------------------------------------------------------------------------*/ +/** + * \brief Let mote execute one "block" of code (tick mote). + * + * This is a JNI function and should only be called via the + * responsible Java part (MoteType.java). + */ +JNIEXPORT void JNICALL +Java_se_sics_cooja_corecomm_[CLASS_NAME]_tick(JNIEnv *env, jobject obj) +{ + int readyexist = 0; + + /* Ugly hack, setting all ready threads to BLOCKED just to discover which ones wake up */ + for(i=0; i tick, time is now %i\n", cooja_time); + + /* Signal all threads to start working again */ + for(i=0; i getConfigXML() { + Vector config = new Vector(); + + Element element; + + // Mote type identifier + element = new Element("motetype_identifier"); + element.setText(getType().getIdentifier()); + config.add(element); + + // Active interface configs (if any) + for (MoteInterface moteInterface : getInterfaces().getAllActiveInterfaces()) { + element = new Element("interface_config"); + element.setText(moteInterface.getClass().getName()); + + Collection interfaceXML = moteInterface.getConfigXML(); + if (interfaceXML != null) { + element.addContent(interfaceXML); + config.add(element); + } + } + + // Passive interface configs (if any) + for (MoteInterface moteInterface : getInterfaces() + .getAllPassiveInterfaces()) { + element = new Element("interface_config"); + element.setText(moteInterface.getClass().getName()); + + Collection interfaceXML = moteInterface.getConfigXML(); + if (interfaceXML != null) { + element.addContent(interfaceXML); + config.add(element); + } + } + + return config; + } + + public boolean setConfigXML(Simulation simulation, + Collection configXML) { + mySimulation = simulation; + + for (Element element : configXML) { + String name = element.getName(); + + if (name.equals("motetype_identifier")) { + myType = (MantisMoteType) simulation.getMoteType(element.getText()); + myMemory = myType.createInitialMemory(); + myInterfaceHandler = new MoteInterfaceHandler((Mote) this, myType + .getMoteInterfaces()); + + } else if (name.equals("interface_config")) { + Class moteInterfaceClass = GUI.currentGUI + .tryLoadClass(this, MoteInterface.class, element.getText().trim()); + + if (moteInterfaceClass == null) { + logger.fatal("Could not load mote interface class: " + + element.getText().trim()); + return false; + } + + MoteInterface moteInterface = myInterfaceHandler + .getInterfaceOfType(moteInterfaceClass); + if (moteInterface != null) + moteInterface.setConfigXML(element.getChildren()); + else + logger + .warn("Can't restore configuration for non-existing interface: " + + moteInterfaceClass.getName()); + } + } + + return true; + } + + public String toString() { + if (getInterfaces().getMoteID() != null) { + return "Mantis Mote, ID=" + getInterfaces().getMoteID().getMoteID(); + } else + return "Mantis Mote, ID=null"; + } + +} diff --git a/tools/cooja/java/se/sics/cooja/mantismote/MantisMoteType.java b/tools/cooja/java/se/sics/cooja/mantismote/MantisMoteType.java new file mode 100644 index 000000000..d273390ec --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/mantismote/MantisMoteType.java @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2006, 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: MantisMoteType.java,v 1.1 2006/11/09 19:31:15 fros4943 Exp $ + */ + +package se.sics.cooja.mantismote; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.io.File; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; +import se.sics.cooja.contikimote.ContikiMoteType; + +/** + * The Mantis mote type holds the native library used to communicate with an + * underlying Mantis system. All communication with that system should always + * pass through this mote type. + *

+ * All core communication with the Mantis mote should be via this class. When a + * mote type is created it allocates a CoreComm to be used with this type. + *

+ * When a new mote type is created an initialization function is run on the + * Mantis system in order to create the initial memory. When a new mote is + * created the createInitialMemory() method should be called to get this initial + * memory for the mote. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Mantis Mote Type") +public class MantisMoteType implements MoteType { + private static Logger logger = Logger.getLogger(MantisMoteType.class); + private Simulation mySimulation = null; + + // Mote type specific information + private String myIdentifier = null; + private String myDescription = null; + private String myObjectFilename = null; + private SectionMoteMemory myInitialMemory = null; + private Vector> moteInterfaceClasses = null; + + // Core communication variables + private String libraryClassName = null; + private int offsetRelToAbs = 0; + private CoreComm myCoreComm = null; + + // Variable name to address mappings + private Properties varAddresses = new Properties(); + + /** + * Creates a new uninitialized Mantis mote type. This mote type's doInit + * method must be called and succeed before it can be used. + */ + public MantisMoteType() { + } + + /** + * Creates a new initialized Mantis mote type. The given library file is + * loaded by the first available CoreComm. Each mote generated from this mote + * type will have the interfaces specified in the given mote interface class + * list. + * + * @param libFile + * Library file to load + * @param objFile + * Object file + * @param moteInterfaceClasses + * List of mote interfaces + */ + public MantisMoteType(File libFile, File objFile, + Vector> moteInterfaceClasses) { + if (!doInit(libFile, objFile, moteInterfaceClasses)) + logger.fatal("Mantis mote type creation failed!"); + } + + /** + * This is an mote type initialization method and should normally never be + * called by any other part than the mote type constructor. It is called from + * the constructor with an identifier argument, but not from the standard + * constructor. This method may be called from the simulator when loading + * configuration files, and the libraries must be recompiled. + * + * This method allocates a core communicator, loads the Mantis library file, + * creates variable name to address mappings and finally creates the Mantis + * mote initial memory. + * + * @param libFile Library file + * @param objFile Object file + * @param moteInterfaceClasses Mote interface classes + * @return True if initialization ok, false otherwise + */ + protected boolean doInit(File libFile, File objFile, + Vector> moteInterfaceClasses) { + myObjectFilename = objFile.getAbsolutePath(); + myIdentifier = libFile.getName(); + myDescription = libFile.getAbsolutePath(); + + // Allocate core communicator class + libraryClassName = CoreComm.getAvailableClassName(); + myCoreComm = CoreComm.createCoreComm(libraryClassName, libFile); + + // Parse variable name to addresses mappings using nm + varAddresses.clear(); + Vector nmData = ContikiMoteType.loadNmData(libFile); + if (nmData == null || !ContikiMoteType.parseNmData(nmData, varAddresses)) { + logger.fatal("Nm response parsing failed"); + return false; + } + + // TODO Bug. Both sections sizes must be > 0! + + // Parse section offsets and sizes using objdump + Vector objdumpData = ContikiMoteType.loadObjdumpData(libFile); + int relDataSectionAddr = -1; + int dataSectionSize = -1; + int relBssSectionAddr = -1; + int bssSectionSize = -1; + String dataRegExp = "^[ \t]*[0-9]*[ \t]*.data[ \t]*([0-9A-Fa-f]*)[ \t]*[0-9A-Fa-f]*[ \t]*([0-9A-Fa-f]*)[ \t]*[0-9A-Fa-f]*[ \t]*"; + String bssRegExp = "^[ \t]*[0-9]*[ \t]*.bss[ \t]*([0-9A-Fa-f]*)[ \t]*[0-9A-Fa-f]*[ \t]*([0-9A-Fa-f]*)[ \t]*[0-9A-Fa-f]*[ \t]*"; + Pattern dataPattern = Pattern.compile(dataRegExp); + Pattern bssPattern = Pattern.compile(bssRegExp); + Matcher matcher; + for (String objdumpLine: objdumpData) { + matcher = dataPattern.matcher(objdumpLine); + if (matcher.find()) { + String size = matcher.group(1); + String offset = matcher.group(2); + dataSectionSize = Integer.parseInt(size, 16); + relDataSectionAddr = Integer.parseInt(offset, 16); + } + matcher = bssPattern.matcher(objdumpLine); + if (matcher.find()) { + String size = matcher.group(1); + String offset = matcher.group(2); + bssSectionSize = Integer.parseInt(size, 16); + relBssSectionAddr = Integer.parseInt(offset, 16); + } + } + + if (relDataSectionAddr == -1) { + logger.fatal("Data section address parsing failed"); + return false; + } + if (dataSectionSize == -1) { + logger.fatal("Data section size parsing failed"); + return false; + } + if (relBssSectionAddr == -1) { + logger.fatal("BSS section address parsing failed"); + return false; + } + if (bssSectionSize == -1) { + logger.fatal("BSS section size parsing failed"); + return false; + } + + // Get offset between relative and absolute addresses + offsetRelToAbs = myCoreComm.getReferenceAbsAddr() - (Integer) varAddresses.get("referenceVar"); + + // Read initial memory from Mantis system + byte[] initialDataSection = new byte[dataSectionSize]; + myCoreComm.getMemory(relDataSectionAddr + offsetRelToAbs, dataSectionSize, initialDataSection); + byte[] initialBssSection = new byte[bssSectionSize]; + myCoreComm.getMemory(relBssSectionAddr + offsetRelToAbs, bssSectionSize, initialBssSection); + + // Store initial memory for later use + myInitialMemory = new SectionMoteMemory(varAddresses); + myInitialMemory.setMemorySegment(relDataSectionAddr, initialDataSection); + myInitialMemory.setMemorySegment(relBssSectionAddr, initialBssSection); + + this.moteInterfaceClasses = moteInterfaceClasses; + + return true; + } + + /** + * Creates and returns a copy of this mote type's initial memory (just after + * the init function has been run). When a new mote is created it should get + * it's memory from here. + * + * @return Initial memory of a mote type + */ + public SectionMoteMemory createInitialMemory() { + return myInitialMemory.clone(); + } + + /** + * Ticks the currently loaded mote. This should not be used directly, but + * rather via MantisMote.tick(). + */ + public void tick() { + myCoreComm.tick(); + } + + /** + * Copy core memory to given memory. This should not be used directly, but + * instead via MantisMote.getMemory(). + * + * @param mem + * Memory to set + */ + public void getCoreMemory(SectionMoteMemory mem) { + for (int i = 0; i < mem.getNumberOfSections(); i++) { + int startAddr = mem.getStartAddrOfSection(i); + int size = mem.getSizeOfSection(i); + byte[] data = mem.getDataOfSection(i); + + getCoreMemory(startAddr + offsetRelToAbs, + size, data); + } + } + + /** + * Copy given memory to the Mantis system. This should not be used directly, + * but instead via MantisMote.setMemory(). + * + * @param mem + * New memory + */ + public void setCoreMemory(SectionMoteMemory mem) { + for (int i = 0; i < mem.getNumberOfSections(); i++) { + setCoreMemory(mem.getStartAddrOfSection(i) + offsetRelToAbs, mem + .getSizeOfSection(i), mem.getDataOfSection(i)); + } + } + + private void getCoreMemory(int start, int length, byte[] data) { + myCoreComm.getMemory(start, length, data); + } + + private void setCoreMemory(int start, int length, byte[] mem) { + myCoreComm.setMemory(start, length, mem); + } + + + /** + * Returns all mote interfaces of this mote type + * + * @return All mote interfaces + */ + public Vector> getMoteInterfaces() { + return moteInterfaceClasses; + } + + public String getDescription() { + return myDescription; + } + + public void setDescription(String description) { + myDescription = description; + } + + public String getIdentifier() { + return myIdentifier; + } + + public void setIdentifier(String identifier) { + myIdentifier = identifier; + } + + public String getObjectFilename() { + return myObjectFilename; + } + + public void setObjectFilename(String objectFilename) { + myObjectFilename = objectFilename; + } + + public JPanel getTypeVisualizer() { + JPanel panel = new JPanel(); + JLabel label = new JLabel(); + JPanel smallPane; + + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + + // Identifier + smallPane = new JPanel(new BorderLayout()); + label = new JLabel("Identifier"); + smallPane.add(BorderLayout.WEST, label); + label = new JLabel(myIdentifier); + smallPane.add(BorderLayout.EAST, label); + panel.add(smallPane); + + // Description + smallPane = new JPanel(new BorderLayout()); + label = new JLabel("Description"); + smallPane.add(BorderLayout.WEST, label); + label = new JLabel(myDescription); + smallPane.add(BorderLayout.EAST, label); + panel.add(smallPane); + + // Object file + smallPane = new JPanel(new BorderLayout()); + label = new JLabel("Object file"); + smallPane.add(BorderLayout.WEST, label); + label = new JLabel(myObjectFilename); + smallPane.add(BorderLayout.EAST, label); + panel.add(smallPane); + + // Library class name + smallPane = new JPanel(new BorderLayout()); + label = new JLabel("JNI Class"); + smallPane.add(BorderLayout.WEST, label); + label = new JLabel(libraryClassName); + smallPane.add(BorderLayout.EAST, label); + panel.add(smallPane); + + panel.add(Box.createRigidArea(new Dimension(0, 5))); + return panel; + } + + public PlatformConfig getConfig() { + logger.debug("MantisMoteType::getConfig"); + return null; + } + + public Mote generateMote(Simulation simulation) { + return new MantisMote(this, mySimulation); + } + + public boolean configureAndInit(JFrame parentFrame, Simulation simulation) { + return MantisMoteTypeDialog.showDialog(parentFrame, simulation, this); + } + + public Collection getConfigXML() { + Vector config = new Vector(); + + Element element; + + // Identifier + element = new Element("identifier"); + element.setText(getIdentifier()); + config.add(element); + + // Description + element = new Element("description"); + element.setText(getDescription()); + config.add(element); + + // Object file + element = new Element("objectfile"); + element.setText(getObjectFilename()); + config.add(element); + + return config; + } + + public boolean setConfigXML(Simulation simulation, + Collection configXML) { + mySimulation = simulation; + + for (Element element : configXML) { + String name = element.getName(); + + if (name.equals("identifier")) { + myIdentifier = element.getText(); + } else if (name.equals("description")) { + myDescription = element.getText(); + } else if (name.equals("objectfile")) { + myObjectFilename = element.getText(); + } else { + logger.fatal("Unrecognized entry in loaded configuration: " + name); + } + } + + boolean createdOK = configureAndInit(GUI.frame, simulation); + return createdOK; + } + +} diff --git a/tools/cooja/java/se/sics/cooja/mantismote/MantisMoteTypeDialog.java b/tools/cooja/java/se/sics/cooja/mantismote/MantisMoteTypeDialog.java new file mode 100644 index 000000000..33bd88d71 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/mantismote/MantisMoteTypeDialog.java @@ -0,0 +1,793 @@ +/* + * Copyright (c) 2006, 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: MantisMoteTypeDialog.java,v 1.1 2006/11/09 19:31:14 fros4943 Exp $ + */ + +package se.sics.cooja.mantismote; + +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.util.*; +import javax.swing.*; +import javax.swing.event.*; +import org.apache.log4j.Logger; + +import se.sics.cooja.*; +import se.sics.cooja.dialogs.MessageList; + +/** + * A dialog for configuring Mantis mote types and compiling KMantis mote type + * libraries. + * + * The dialog takes a Mantis mote type as argument and pre-selects the values + * already set in that mote type before showing the dialog. Any changes made to + * the settings are written to the mote type if the compilation is successful + * and the user presses OK. + * + * This dialog uses external tools to scan for sources and compile libraries. + * + * @author Fredrik Osterlind + */ +public class MantisMoteTypeDialog extends JDialog { + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(MantisMoteTypeDialog.class); + + private MoteTypeEventHandler myEventHandler = new MoteTypeEventHandler(); + private Thread compilationThread; + + /** + * Suggested mote type identifier prefix + */ + public static final String ID_PREFIX = "mtype"; + + private final static int LABEL_WIDTH = 170; + private final static int LABEL_HEIGHT = 15; + + private MantisMoteType myMoteType = null; + + private JTextField textID, textOutputFiles, textDescription, textMantisBinary; + private JButton createButton, compileButton; + + private File objFile = null; + private File workingDir = null; + private File libFile = null; + private File srcFile = null; + + private Vector> moteInterfaceClasses = null; + + private boolean settingsOK = false; // Do all settings seem correct? + private boolean compilationSucceded = false; // Did compilation succeed? + private boolean libraryCreatedOK = false; // Was a library created? + + private Vector allOtherTypes = null; // Used to check for conflicting parameters + + private MantisMoteTypeDialog myDialog; + + /** + * Shows a dialog for configuring a Mantis mote type. + * + * @param parentFrame + * Parent frame for dialog + * @param simulation + * Simulation holding (or that will hold) mote type + * @param moteTypeToConfigure + * Mote type to configure + * @return True if mote type configuration succeded and library is ready to be loaded + */ + public static boolean showDialog(Frame parentFrame, Simulation simulation, + MantisMoteType moteTypeToConfigure) { + + final MantisMoteTypeDialog myDialog = new MantisMoteTypeDialog( + parentFrame); + + myDialog.myMoteType = moteTypeToConfigure; + myDialog.allOtherTypes = simulation.getMoteTypes(); + + // Set identifier of mote type + if (moteTypeToConfigure.getIdentifier() != null) { + // Identifier already preset, assuming recompilation of mote type library + // Use preset identifier (read-only) + myDialog.textID.setText(moteTypeToConfigure.getIdentifier()); + myDialog.textID.setEditable(false); + myDialog.textID.setEnabled(false); + + // Change title to indicate this is a recompilation + myDialog.setTitle("Recompile Mote Type"); + } else { + // Suggest new identifier + int counter = 0; + String testIdentifier = ""; + boolean identifierOK = false; + while (!identifierOK) { + counter++; + testIdentifier = ID_PREFIX + counter; + identifierOK = true; + + // Check if identifier is already used by some other type + for (MoteType existingMoteType : myDialog.allOtherTypes) { + if (existingMoteType != myDialog.myMoteType + && existingMoteType.getIdentifier().equals(testIdentifier)) { + identifierOK = false; + break; + } + } + } + + myDialog.textID.setText(testIdentifier); + } + + // Set preset description of mote type + if (moteTypeToConfigure.getDescription() != null) { + myDialog.textDescription.setText(moteTypeToConfigure.getDescription()); + } else { + myDialog.textDescription.setText("mantis type, id=" + myDialog.textID.getText()); + } + + // Set preset object file of mote type + if (moteTypeToConfigure.getObjectFilename() != null) { + myDialog.textMantisBinary.setText(moteTypeToConfigure.getObjectFilename()); + } + + // Load all mote interface classes + String[] moteInterfaces = GUI.currentGUI.getPlatformConfig().getStringArrayValue(MantisMoteType.class, "MOTE_INTERFACES"); + myDialog.moteInterfaceClasses = new Vector>(); + for (String moteInterface : moteInterfaces) { + try { + Class newMoteInterfaceClass = + GUI.currentGUI.tryLoadClass(GUI.currentGUI, MoteInterface.class, moteInterface); + myDialog.moteInterfaceClasses.add(newMoteInterfaceClass); + /*logger.info("Loaded Mantis mote interface: " + newMoteInterfaceClass);*/ + } catch (Exception e) { + logger.fatal("Failed to load mote interface, aborting: " + moteInterface + ", " + e.getMessage()); + return false; + } + } + + // Set position and focus of dialog + myDialog.pack(); + myDialog.setLocationRelativeTo(parentFrame); + myDialog.textDescription.requestFocus(); + myDialog.textDescription.select(0, myDialog.textDescription.getText().length()); + myDialog.pathsWereUpdated(); + myDialog.setVisible(true); + + if (myDialog.myMoteType != null) { + // Library was compiled and loaded + return true; + } + return false; + } + + private MantisMoteTypeDialog(Frame frame) { + super(frame, "Configure Mantis Mote Type", true); + + myDialog = this; + + JLabel label; + JPanel mainPane = new JPanel(); + mainPane.setLayout(new BoxLayout(mainPane, BoxLayout.Y_AXIS)); + JPanel smallPane; + JTextField textField; + JButton button; + + // BOTTOM BUTTON PART + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS)); + buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + + button = new JButton("Compile"); + button.setActionCommand("compile"); + button.addActionListener(myEventHandler); + compileButton = button; + this.getRootPane().setDefaultButton(button); + buttonPane.add(button); + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + + buttonPane.add(Box.createHorizontalGlue()); + + button = new JButton("Clean"); + button.setActionCommand("clean"); + button.addActionListener(myEventHandler); + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + buttonPane.add(button); + + button = new JButton("Cancel"); + button.setActionCommand("cancel"); + button.addActionListener(myEventHandler); + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + buttonPane.add(button); + + button = new JButton("Create"); + button.setEnabled(libraryCreatedOK); + button.setActionCommand("create"); + button.addActionListener(myEventHandler); + createButton = button; + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + buttonPane.add(button); + + // MAIN PART + + // Identifier + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Identifier"); + label.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); + + textField = new JTextField(); + + textField.setText(""); + + textField.getDocument().addDocumentListener(myEventHandler); + textID = textField; + label.setLabelFor(textField); + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(Box.createHorizontalGlue()); + smallPane.add(textField); + + mainPane.add(smallPane); + + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // Output filenames + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Output files"); + label.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); + + textField = new JTextField(); + textField.setText(""); + textField.setEnabled(false); + textOutputFiles = textField; + label.setLabelFor(textField); + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(Box.createHorizontalGlue()); + smallPane.add(textField); + + mainPane.add(smallPane); + + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // Description + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Description"); + label.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); + + textField = new JTextField(); + textField.setBackground(Color.GREEN); + textField.setText(""); + textField.getDocument().addDocumentListener(myEventHandler); + textDescription = textField; + label.setLabelFor(textField); + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(Box.createHorizontalGlue()); + smallPane.add(textField); + + mainPane.add(smallPane); + + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // Mantis binary + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Mantis x86 object"); + label.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); + + textField = new JTextField(); + textField.setText(""); + textField.getDocument().addDocumentListener(myEventHandler); + textMantisBinary = textField; + label.setLabelFor(textField); + + button = new JButton("Browse"); + button.setActionCommand("browsemantis"); + button.addActionListener(myEventHandler); + + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(Box.createHorizontalGlue()); + smallPane.add(textField); + smallPane.add(button); + mainPane.add(smallPane); + + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + mainPane.add(Box.createVerticalGlue()); + + // Add everything! + mainPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + Container contentPane = getContentPane(); + contentPane.add(mainPane, BorderLayout.NORTH); + contentPane.add(buttonPane, BorderLayout.SOUTH); + } + + /** + * Tries to compile library using current settings. + */ + public void doCompileCurrentSettings() { + libraryCreatedOK = false; + + JPanel progressPanel = new JPanel(new BorderLayout()); + final JDialog progressDialog = new JDialog(myDialog, null); + JProgressBar progressBar; + JButton button; + final MessageList taskOutput; + progressDialog.setLocationRelativeTo(myDialog); + + progressBar = new JProgressBar(0, 100); + progressBar.setValue(0); + progressBar.setStringPainted(true); + progressBar.setIndeterminate(true); + + taskOutput = new MessageList(); + + button = new JButton("Close/Abort"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (compilationThread != null && compilationThread.isAlive()) { + compilationThread.interrupt(); + } + progressDialog.dispose(); + } + }); + + progressPanel.add(BorderLayout.CENTER, new JScrollPane(taskOutput)); + progressPanel.add(BorderLayout.NORTH, progressBar); + progressPanel.add(BorderLayout.SOUTH, button); + progressPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + progressPanel.setVisible(true); + + progressDialog.getContentPane().add(progressPanel); + progressDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + progressDialog.pack(); + + progressDialog.getRootPane().setDefaultButton(button); + progressDialog.setVisible(true); + + // Generate main mantis source file + try { + // Remove old file is existing + if (srcFile.exists()) { + srcFile.delete(); + } + + if (srcFile.exists()) { + throw new Exception("could not remove old source file"); + } + + generateSourceFile(srcFile); + + if (!srcFile.exists()) { + throw new Exception("source file not created"); + } + } catch (Exception e) { + libraryCreatedOK = false; + progressBar.setBackground(Color.ORANGE); + if (e.getMessage() != null) + progressBar.setString("source file generation failed: " + e.getMessage()); + else + progressBar.setString("source file generation failed"); + + progressBar.setIndeterminate(false); + progressBar.setValue(0); + createButton.setEnabled(libraryCreatedOK); + return; + } + + // Test compile shared library + progressBar.setString("..compiling.."); + + if (libFile.exists()) { + libFile.delete(); + } + + compilationThread = new Thread(new Runnable() { + public void run() { + compilationSucceded = + MantisMoteTypeDialog.compileLibrary( + libFile, + objFile, + srcFile, + workingDir, + taskOutput.getInputStream(MessageList.NORMAL), + taskOutput.getInputStream(MessageList.ERROR)); + } + }, "compilation thread"); + compilationThread.start(); + + while (compilationThread.isAlive()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // NOP + } + } + + if (!compilationSucceded) { + if (libFile.exists()) { + libFile.delete(); + } + libraryCreatedOK = false; + } else { + libraryCreatedOK = true; + if (!libFile.exists()) + libraryCreatedOK = false; + } + + if (libraryCreatedOK) { + progressBar.setBackground(Color.GREEN); + progressBar.setString("compilation succeded"); + button.grabFocus(); + myDialog.getRootPane().setDefaultButton(createButton); + } else { + progressBar.setBackground(Color.ORANGE); + progressBar.setString("compilation failed"); + myDialog.getRootPane().setDefaultButton(compileButton); + } + progressBar.setIndeterminate(false); + progressBar.setValue(0); + createButton.setEnabled(libraryCreatedOK); + } + + /** + * Generates new source file by reading default source template and replacing + * certain field in order to be loadable from given Java class. + * + * @param outputFile Source file to create + * @throws Exception + */ + public static void generateSourceFile(File outputFile) + throws Exception { + + // CHECK JNI CLASS AVAILABILITY + String libString = CoreComm.getAvailableClassName(); + if (libString == null) { + logger.fatal("No more libraries can be loaded!"); + throw new Exception("Maximum number of mote types already exist"); + } + + // GENERATE NEW FILE + BufferedWriter destFile = null; + BufferedReader sourceFile = null; + try { + Reader reader; + String mainTemplate = GUI + .getExternalToolsSetting("MANTIS_MAIN_TEMPLATE_FILENAME"); + if ((new File(mainTemplate)).exists()) { + reader = new FileReader(mainTemplate); + } else { + InputStream input = MantisMoteTypeDialog.class + .getResourceAsStream('/' + mainTemplate); + if (input == null) { + throw new FileNotFoundException(mainTemplate + " not found"); + } + reader = new InputStreamReader(input); + } + + sourceFile = new BufferedReader(reader); + + destFile = new BufferedWriter(new OutputStreamWriter( + new FileOutputStream(outputFile))); + + // Replace fields in template + String line; + while ((line = sourceFile.readLine()) != null) { + line = line.replaceFirst("\\[CLASS_NAME\\]", libString); + destFile.write(line + "\n"); + } + + destFile.close(); + sourceFile.close(); + } catch (Exception e) { + try { + if (destFile != null) + destFile.close(); + if (sourceFile != null) + sourceFile.close(); + } catch (Exception e2) { + } + + // Forward exception + throw e; + } + } + + /** + * Compiles a mote type shared library using the standard Mantis makefile. + * + * @param libFile Library file to create + * @param binFile Binary file to link against + * @param sourceFile Source file to compile + * @param workingDir Working directory + * @param outputStream + * Output stream from compilation (optional) + * @param errorStream + * Error stream from compilation (optional) + * @return True if compilation succeeded, false otherwise + */ + public static boolean compileLibrary(File libFile, File binFile, File sourceFile, File workingDir, + final PrintStream outputStream, final PrintStream errorStream) { + + // Check needed files + if (!workingDir.exists()) { + if (errorStream != null) + errorStream.println("Bad paths"); + logger.fatal("Working directory does not exist"); + return false; + } + if (!workingDir.isDirectory()) { + if (errorStream != null) + errorStream.println("Bad paths"); + logger.fatal("Working directory is not a directory"); + return false; + } + + if (libFile.exists()) { + if (errorStream != null) + errorStream.println("Bad output filenames"); + logger.fatal("Library already exists"); + return false; + } + + if (!sourceFile.exists()) { + if (errorStream != null) + errorStream.println("Bad dependency files"); + logger.fatal("Source file not found"); + return false; + } + + if (!binFile.exists()) { + if (errorStream != null) + errorStream.println("Bad dependency files"); + logger.fatal("Link object file not found"); + return false; + } + + if (CoreComm.hasLibraryFileBeenLoaded(libFile)) { + if (errorStream != null) + errorStream.println("Bad output filenames"); + logger.fatal("A library has already been loaded with the same name before"); + return false; + } + + try { + // Call make file + String[] cmd = new String[]{ + GUI.getExternalToolsSetting("PATH_MAKE"), + libFile.getName()}; + + String[] env = new String[]{ + "COOJA_LINKFILE=" + binFile.getName(), + "COOJA_SOURCE=" + sourceFile.getName(), + "PATH=" + System.getenv("PATH")}; + + Process p = Runtime.getRuntime().exec(cmd, env, workingDir); + + final BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream())); + final BufferedReader err = new BufferedReader(new InputStreamReader(p.getErrorStream())); + + Thread readInput = new Thread(new Runnable() { + public void run() { + String readLine; + try { + while ((readLine = input.readLine()) != null) { + if (outputStream != null && readLine != null) + outputStream.println(readLine); + } + } catch (IOException e) { + logger.warn("Error while reading from process"); + } + } + }, "read input stream thread"); + + Thread readError = new Thread(new Runnable() { + public void run() { + String readLine; + try { + while ((readLine = err.readLine()) != null) { + if (errorStream != null && readLine != null) + errorStream.println(readLine); + } + } catch (IOException e) { + logger.warn("Error while reading from process"); + } + } + }, "read input stream thread"); + + readInput.start(); + readError.start(); + + while (readInput.isAlive() || readError.isAlive()) { + Thread.sleep(100); + } + + input.close(); + err.close(); + + p.waitFor(); + if (p.exitValue() != 0) { + logger.fatal("Make file returned error: " + p.exitValue()); + return false; + } + } catch (Exception e) { + logger.fatal("Error while compiling library: " + e); + return false; + } + return true; + } + + private void pathsWereUpdated() { + updateVisualFields(); + } + + private void updateVisualFields() { + settingsOK = true; + + // Check for non-unique identifier + textID.setBackground(Color.WHITE); + textID.setToolTipText(null); + + for (MoteType otherType : allOtherTypes) { + if (otherType != myMoteType + && otherType.getIdentifier().equalsIgnoreCase(textID.getText())) { + textID.setBackground(Color.RED); + textID.setToolTipText("Conflicting name - must be unique"); + settingsOK = false; + break; + } + } + + // Check for non-unique description + textDescription.setBackground(Color.WHITE); + textDescription.setToolTipText(null); + + for (MoteType otherType : allOtherTypes) { + if (otherType != myMoteType + && otherType.getDescription().equals(textDescription.getText())) { + textDescription.setBackground(Color.RED); + textDescription.setToolTipText("Conflicting name - must be unique"); + settingsOK = false; + break; + } + } + + // Check that binary exists + textMantisBinary.setBackground(Color.WHITE); + textMantisBinary.setToolTipText(null); + + objFile = new File(textMantisBinary.getText()); + workingDir = objFile.getParentFile(); + libFile = new File(workingDir, textID.getText() + ".library"); + srcFile = new File(workingDir, textID.getText() + ".c"); + // TODO Check that file is correct type (.o or something) + if (objFile == null || !objFile.exists()) { + textMantisBinary.setBackground(Color.RED); + textMantisBinary.setToolTipText("Incorrect object file"); + objFile = null; + libFile = null; + srcFile = null; + workingDir = null; + settingsOK = false; + } + + // Update output text field + if (settingsOK) { + textOutputFiles.setText(libFile.getName() + ", " + srcFile.getName() + ", " + textID.getText() + ".o"); + } else { + textOutputFiles.setText(""); + } + + createButton.setEnabled(libraryCreatedOK = false); + compileButton.setEnabled(settingsOK); + } + + + private class MoteTypeEventHandler + implements + ActionListener, + DocumentListener { + public void insertUpdate(DocumentEvent e) { + if (myDialog.isVisible()) + javax.swing.SwingUtilities.invokeLater(new Runnable() { + public void run() { + pathsWereUpdated(); + } + }); + } + public void removeUpdate(DocumentEvent e) { + if (myDialog.isVisible()) + javax.swing.SwingUtilities.invokeLater(new Runnable() { + public void run() { + pathsWereUpdated(); + } + }); + } + public void changedUpdate(DocumentEvent e) { + if (myDialog.isVisible()) + javax.swing.SwingUtilities.invokeLater(new Runnable() { + public void run() { + pathsWereUpdated(); + } + }); + } + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("cancel")) { + // Cancel creation of mote type + myMoteType = null; + dispose(); + } else if (e.getActionCommand().equals("clean")) { + // Delete any created intermediate files + // TODO Not implemented + logger.fatal("Clean functionality not implemented"); + } else if (e.getActionCommand().equals("create")) { + // Create mote type and set related fields + boolean ret = myMoteType.doInit(libFile, objFile, moteInterfaceClasses); + myMoteType.setDescription(textDescription.getText()); + myMoteType.setIdentifier(textID.getText()); + if (ret) { + dispose(); + } else { + logger.fatal("Mote type creation failed."); + } + } else if (e.getActionCommand().equals("compile")) { + compileButton.requestFocus(); + Thread testSettingsThread = new Thread(new Runnable() { + public void run() { + doCompileCurrentSettings(); + } + }, "test settings thread"); + testSettingsThread.start(); + } else if (e.getActionCommand().equals("browsemantis")) { + JFileChooser fc = new JFileChooser(); + fc.setCurrentDirectory(new java.io.File(".")); + fc.setFileSelectionMode(JFileChooser.FILES_ONLY); + fc.setDialogTitle("Mantis binary to link against"); + + if (fc.showOpenDialog(myDialog) == JFileChooser.APPROVE_OPTION) { + textMantisBinary.setText(fc.getSelectedFile().getPath()); + } + createButton.setEnabled(libraryCreatedOK = false); + pathsWereUpdated(); + } else + logger.warn("Unhandled action: " + e.getActionCommand()); + + createButton.setEnabled(libraryCreatedOK = false); + + } + } + +}