diff --git a/tools/cooja/apps/serial_socket/cooja.config b/tools/cooja/apps/serial_socket/cooja.config index 10b86f27f..8c492dc83 100644 --- a/tools/cooja/apps/serial_socket/cooja.config +++ b/tools/cooja/apps/serial_socket/cooja.config @@ -1,2 +1,2 @@ -org.contikios.cooja.Cooja.PLUGINS = + SerialSocketClient SerialSocketServer +org.contikios.cooja.Cooja.PLUGINS = + org.contikios.cooja.serialsocket.SerialSocketClient org.contikios.cooja.serialsocket.SerialSocketServer org.contikios.cooja.Cooja.JARFILES = + serial_socket.jar diff --git a/tools/cooja/apps/serial_socket/java/SerialSocketClient.java b/tools/cooja/apps/serial_socket/java/SerialSocketClient.java deleted file mode 100644 index c3333c0b0..000000000 --- a/tools/cooja/apps/serial_socket/java/SerialSocketClient.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright (c) 2010, 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.Dimension; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.net.Socket; -import java.util.Collection; -import java.util.Observable; -import java.util.Observer; - -import javax.swing.BorderFactory; -import javax.swing.Box; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.SwingUtilities; - -import org.apache.log4j.Logger; -import org.jdom.Element; - -import org.contikios.cooja.ClassDescription; -import org.contikios.cooja.Cooja; -import org.contikios.cooja.Mote; -import org.contikios.cooja.MotePlugin; -import org.contikios.cooja.PluginType; -import org.contikios.cooja.Simulation; -import org.contikios.cooja.VisPlugin; -import org.contikios.cooja.interfaces.SerialPort; - -/** - * Socket to simulated serial port forwarder. Client version. - * - * @author Fredrik Osterlind - */ -@ClassDescription("Serial Socket (CLIENT)") -@PluginType(PluginType.MOTE_PLUGIN) -public class SerialSocketClient extends VisPlugin implements MotePlugin { - private static final long serialVersionUID = 1L; - private static Logger logger = Logger.getLogger(SerialSocketClient.class); - - private final static int LABEL_WIDTH = 100; - private final static int LABEL_HEIGHT = 15; - - public final static String SERVER_HOST = "localhost"; - public final static int SERVER_PORT = 1234; - - private SerialPort serialPort; - private Observer serialDataObserver; - - private JLabel statusLabel, inLabel, outLabel; - private int inBytes = 0, outBytes = 0; - - private Socket socket; - private DataInputStream in; - private DataOutputStream out; - - private Mote mote; - - public SerialSocketClient(Mote mote, Simulation simulation, final Cooja gui) { - super("Serial Socket (CLIENT) (" + mote + ")", gui, false); - this.mote = mote; - - /* GUI components */ - if (Cooja.isVisualized()) { - Box northBox = Box.createHorizontalBox(); - northBox.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - statusLabel = configureLabel(northBox, "", ""); - - Box mainBox = Box.createHorizontalBox(); - mainBox.setBorder(BorderFactory.createEmptyBorder(0, 5, 5, 5)); - inLabel = configureLabel(mainBox, "socket -> mote:", "0 bytes"); - outLabel = configureLabel(mainBox, "mote -> socket", "0 bytes"); - - getContentPane().add(BorderLayout.NORTH, northBox); - getContentPane().add(BorderLayout.CENTER, mainBox); - pack(); - } - - /* Mote serial port */ - serialPort = (SerialPort) mote.getInterfaces().getLog(); - if (serialPort == null) { - throw new RuntimeException("No mote serial port"); - } - - try { - logger.info("Connecting: " + SERVER_HOST + ":" + SERVER_PORT); - socket = new Socket(SERVER_HOST, SERVER_PORT); - in = new DataInputStream(socket.getInputStream()); - out = new DataOutputStream(socket.getOutputStream()); - out.flush(); - startSocketReadThread(in); - } catch (Exception e) { - throw (RuntimeException) new RuntimeException( - "Connection error: " + e.getMessage()).initCause(e); - } - - /* Observe serial port for outgoing data */ - serialPort.addSerialDataObserver(serialDataObserver = new Observer() { - public void update(Observable obs, Object obj) { - try { - if (out == null) { - return; - } - out.write(serialPort.getLastSerialData()); - out.flush(); - outBytes++; - if (Cooja.isVisualized()) { - outLabel.setText(outBytes + " bytes"); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - }); - } - - private void startSocketReadThread(final DataInputStream in) { - /* Forward data: virtual port -> mote */ - Thread incomingDataThread = new Thread(new Runnable() { - public void run() { - int numRead = 0; - byte[] data = new byte[1024]; - logger.info("Forwarder: socket -> serial port"); - while (true) { - numRead = -1; - try { - numRead = in.read(data); - } catch (IOException e) { - e.printStackTrace(); - return; - } - - if (numRead >= 0) { - for (int i=0; i < numRead; i++) { - serialPort.writeByte(data[i]); - } - inBytes += numRead; - if (Cooja.isVisualized()) { - inLabel.setText(inBytes + " bytes"); - } - } else { - logger.warn("Incoming data thread shut down"); - cleanup(); - break; - } - } - } - }); - incomingDataThread.start(); - } - - private JLabel configureLabel(JComponent pane, String desc, String value) { - JPanel smallPane = new JPanel(new BorderLayout()); - JLabel label = new JLabel(desc); - label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); - smallPane.add(BorderLayout.WEST, label); - label = new JLabel(value); - label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); - smallPane.add(BorderLayout.CENTER, label); - pane.add(smallPane); - return label; - } - - public boolean setConfigXML(Collection configXML, boolean visAvailable) { - return true; - } - - public Collection getConfigXML() { - return null; - } - - private void cleanup() { - serialPort.deleteSerialDataObserver(serialDataObserver); - - try { - if (socket != null) { - socket.close(); - socket = null; - } - } catch (IOException e1) { - } - try { - if (in != null) { - in.close(); - in = null; - } - } catch (IOException e) { - } - try { - if (out != null) { - out.close(); - out = null; - } - } catch (IOException e) { - } - - SwingUtilities.invokeLater(new Runnable() { - public void run() { - SerialSocketClient.this.setTitle(SerialSocketClient.this.getTitle() + " *DISCONNECTED*"); - statusLabel.setText("Disconnected from server"); - } - }); - } - - public void closePlugin() { - cleanup(); - } - - public Mote getMote() { - return mote; - } -} - diff --git a/tools/cooja/apps/serial_socket/java/SerialSocketServer.java b/tools/cooja/apps/serial_socket/java/SerialSocketServer.java deleted file mode 100644 index b12197ee0..000000000 --- a/tools/cooja/apps/serial_socket/java/SerialSocketServer.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright (c) 2010, 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.Dimension; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.net.ServerSocket; -import java.net.Socket; -import java.util.Collection; -import java.util.Observable; -import java.util.Observer; - -import javax.swing.BorderFactory; -import javax.swing.Box; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.SwingUtilities; -import javax.swing.Timer; - -import org.apache.log4j.Logger; -import org.jdom.Element; - -import org.contikios.cooja.ClassDescription; -import org.contikios.cooja.Cooja; -import org.contikios.cooja.Mote; -import org.contikios.cooja.MotePlugin; -import org.contikios.cooja.PluginType; -import org.contikios.cooja.Simulation; -import org.contikios.cooja.VisPlugin; -import org.contikios.cooja.interfaces.SerialPort; - -/** - * Socket to simulated serial port forwarder. Server version. - * - * @author Fredrik Osterlind - */ -@ClassDescription("Serial Socket (SERVER)") -@PluginType(PluginType.MOTE_PLUGIN) -public class SerialSocketServer extends VisPlugin implements MotePlugin { - private static final long serialVersionUID = 1L; - private static Logger logger = Logger.getLogger(SerialSocketServer.class); - - private final static int LABEL_WIDTH = 100; - private final static int LABEL_HEIGHT = 15; - - public final int LISTEN_PORT; - - private SerialPort serialPort; - private Observer serialDataObserver; - - private JLabel statusLabel, inLabel, outLabel; - private int inBytes = 0, outBytes = 0; - - private ServerSocket server; - private Socket client; - private DataInputStream in; - private DataOutputStream out; - - private Mote mote; - - public SerialSocketServer(Mote mote, Simulation simulation, final Cooja gui) { - super("Serial Socket (SERVER) (" + mote + ")", gui, false); - this.mote = mote; - - updateTimer.start(); - - LISTEN_PORT = 60000 + mote.getID(); - - /* GUI components */ - if (Cooja.isVisualized()) { - Box northBox = Box.createHorizontalBox(); - northBox.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - statusLabel = configureLabel(northBox, "", ""); - - Box mainBox = Box.createHorizontalBox(); - mainBox.setBorder(BorderFactory.createEmptyBorder(0, 5, 5, 5)); - inLabel = configureLabel(mainBox, "socket -> mote:", "0 bytes"); - outLabel = configureLabel(mainBox, "mote -> socket", "0 bytes"); - - getContentPane().add(BorderLayout.NORTH, northBox); - getContentPane().add(BorderLayout.CENTER, mainBox); - pack(); - } - - /* Mote serial port */ - serialPort = (SerialPort) mote.getInterfaces().getLog(); - if (serialPort == null) { - throw new RuntimeException("No mote serial port"); - } - - try { - logger.info("Listening on port: " + LISTEN_PORT); - if (Cooja.isVisualized()) { - statusLabel.setText("Listening on port: " + LISTEN_PORT); - } - server = new ServerSocket(LISTEN_PORT); - new Thread() { - public void run() { - while (server != null) { - try { - client = server.accept(); - in = new DataInputStream(client.getInputStream()); - out = new DataOutputStream(client.getOutputStream()); - out.flush(); - - startSocketReadThread(in); - if (Cooja.isVisualized()) { - statusLabel.setText("Client connected: " + client.getInetAddress()); - } - } catch (IOException e) { - logger.fatal("Listening thread shut down: " + e.getMessage()); - server = null; - cleanupClient(); - break; - } - } - } - }.start(); - } catch (Exception e) { - throw (RuntimeException) new RuntimeException( - "Connection error: " + e.getMessage()).initCause(e); - } - - /* Observe serial port for outgoing data */ - serialPort.addSerialDataObserver(serialDataObserver = new Observer() { - public void update(Observable obs, Object obj) { - try { - if (out == null) { - /*logger.debug("out is null");*/ - return; - } - - out.write(serialPort.getLastSerialData()); - out.flush(); - - outBytes++; - } catch (IOException e) { - cleanupClient(); - } - } - }); - } - - private void startSocketReadThread(final DataInputStream in) { - /* Forward data: virtual port -> mote */ - Thread incomingDataThread = new Thread(new Runnable() { - public void run() { - int numRead = 0; - byte[] data = new byte[1024]; - logger.info("Forwarder: socket -> serial port"); - while (true) { - numRead = -1; - try { - numRead = in.read(data); - } catch (IOException e) { - numRead = -1; - } - - if (numRead >= 0) { - for (int i=0; i < numRead; i++) { - serialPort.writeByte(data[i]); - } - - inBytes += numRead; - } else { - cleanupClient(); - break; - } - } - } - }); - incomingDataThread.start(); - } - - private JLabel configureLabel(JComponent pane, String desc, String value) { - JPanel smallPane = new JPanel(new BorderLayout()); - JLabel label = new JLabel(desc); - label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); - smallPane.add(BorderLayout.WEST, label); - label = new JLabel(value); - label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); - smallPane.add(BorderLayout.CENTER, label); - pane.add(smallPane); - return label; - } - - public boolean setConfigXML(Collection configXML, boolean visAvailable) { - return true; - } - - public Collection getConfigXML() { - return null; - } - - private void cleanupClient() { - try { - if (client != null) { - client.close(); - client = null; - } - } catch (IOException e1) { - } - try { - if (in != null) { - in.close(); - in = null; - } - } catch (IOException e) { - } - try { - if (out != null) { - out.close(); - out = null; - } - } catch (IOException e) { - } - - if (Cooja.isVisualized()) { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - statusLabel.setText("Listening on port: " + LISTEN_PORT); - } - }); - } - } - - private boolean closed = false; - public void closePlugin() { - closed = true; - cleanupClient(); - serialPort.deleteSerialDataObserver(serialDataObserver); - try { - server.close(); - } catch (IOException e) { - } - } - - public Mote getMote() { - return mote; - } - - private static final int UPDATE_INTERVAL = 150; - private Timer updateTimer = new Timer(UPDATE_INTERVAL, new ActionListener() { - public void actionPerformed(ActionEvent e) { - if (closed) { - updateTimer.stop(); - return; - } - - inLabel.setText(inBytes + " bytes"); - outLabel.setText(outBytes + " bytes"); - } - }); -} - diff --git a/tools/cooja/apps/serial_socket/java/org/contikios/cooja/serialsocket/SerialSocketClient.java b/tools/cooja/apps/serial_socket/java/org/contikios/cooja/serialsocket/SerialSocketClient.java new file mode 100644 index 000000000..df8a7dc22 --- /dev/null +++ b/tools/cooja/apps/serial_socket/java/org/contikios/cooja/serialsocket/SerialSocketClient.java @@ -0,0 +1,569 @@ +package org.contikios.cooja.serialsocket; + +/* + * Copyright (c) 2014, TU Braunschweig. + * Copyright (c) 2010, 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.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.Socket; +import java.text.NumberFormat; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Observable; +import java.util.Observer; +import java.util.logging.Level; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JFormattedTextField; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSeparator; +import javax.swing.JTextField; +import javax.swing.SwingUtilities; +import javax.swing.border.EtchedBorder; +import javax.swing.text.NumberFormatter; + +import org.apache.log4j.Logger; +import org.jdom.Element; + +import org.contikios.cooja.ClassDescription; +import org.contikios.cooja.Cooja; +import org.contikios.cooja.Mote; +import org.contikios.cooja.MotePlugin; +import org.contikios.cooja.PluginType; +import org.contikios.cooja.Simulation; +import org.contikios.cooja.VisPlugin; +import org.contikios.cooja.interfaces.SerialPort; + +/** + * Socket to simulated serial port forwarder. Client version. + * + * @author Fredrik Osterlind + * @author Enrico Jorns + */ +@ClassDescription("Serial Socket (CLIENT)") +@PluginType(PluginType.MOTE_PLUGIN) +public class SerialSocketClient extends VisPlugin implements MotePlugin { + private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(SerialSocketClient.class); + + private static final String SERVER_DEFAULT_HOST = "localhost"; + private static final int SERVER_DEFAULT_PORT = 1234; + + private final static int STATUSBAR_WIDTH = 350; + + private static final Color ST_COLOR_UNCONNECTED = Color.DARK_GRAY; + private static final Color ST_COLOR_CONNECTED = new Color(0, 161, 83); + private static final Color ST_COLOR_FAILED = Color.RED; + + private SerialPort serialPort; + private Observer serialDataObserver; + + private JLabel socketToMoteLabel; + private JLabel moteToSocketLabel; + private JLabel socketStatusLabel; + private JTextField serverHostField; + private JFormattedTextField serverPortField; + private JButton serverSelectButton; + + private int inBytes = 0, outBytes = 0; + + private Socket socket; + private DataInputStream in; + private DataOutputStream out; + + private final Mote mote; + private final Simulation simulation; + + public SerialSocketClient(Mote mote, Simulation simulation, final Cooja gui) { + super("Serial Socket (CLIENT) (" + mote + ")", gui, false); + this.mote = mote; + this.simulation = simulation; + + /* GUI components */ + if (Cooja.isVisualized()) { + + setResizable(false); + setLayout(new BorderLayout()); + + // --- Server setup + + GridBagConstraints c = new GridBagConstraints(); + JPanel serverSelectPanel = new JPanel(new GridBagLayout()); + serverSelectPanel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); + + JLabel label = new JLabel("Host:"); + c.gridx = 0; + c.gridy = 0; + c.gridx++; + serverSelectPanel.add(label, c); + + serverHostField = new JTextField(SERVER_DEFAULT_HOST); + serverHostField.setColumns(10); + c.gridx++; + c.weightx = 1.0; + serverSelectPanel.add(serverHostField, c); + + label = new JLabel("Port:"); + c.gridx++; + c.weightx = 0.0; + serverSelectPanel.add(label, c); + + NumberFormat nf = NumberFormat.getIntegerInstance(); + nf.setGroupingUsed(false); + serverPortField = new JFormattedTextField(new NumberFormatter(nf)); + serverPortField.setColumns(5); + serverPortField.setText(String.valueOf(SERVER_DEFAULT_PORT)); + c.gridx++; + serverSelectPanel.add(serverPortField, c); + + serverSelectButton = new JButton("Connect") { // Button for label toggeling + private final String altString = "Disconnect"; + + @Override + public Dimension getPreferredSize() { + String origText = getText(); + Dimension origDim = super.getPreferredSize(); + setText(altString); + Dimension altDim = super.getPreferredSize(); + setText(origText); + return new Dimension(Math.max(origDim.width, altDim.width), origDim.height); + } + }; + c.gridx++; + c.weightx = 0.1; + c.anchor = GridBagConstraints.EAST; + serverSelectPanel.add(serverSelectButton, c); + + c.gridx = 0; + c.gridy++; + c.gridwidth = GridBagConstraints.REMAINDER; + c.fill = GridBagConstraints.HORIZONTAL; + serverSelectPanel.add(new JSeparator(JSeparator.HORIZONTAL), c); + + add(BorderLayout.NORTH, serverSelectPanel); + + // --- Incoming / outgoing info + + JPanel connectionInfoPanel = new JPanel(new GridLayout(0, 2)); + connectionInfoPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + c = new GridBagConstraints(); + + label = new JLabel("socket -> mote: "); + label.setHorizontalAlignment(JLabel.RIGHT); + c.gridx = 0; + c.gridy = 0; + c.anchor = GridBagConstraints.EAST; + connectionInfoPanel.add(label); + + socketToMoteLabel = new JLabel("0 bytes"); + c.gridx++; + c.anchor = GridBagConstraints.WEST; + connectionInfoPanel.add(socketToMoteLabel); + + label = new JLabel("mote -> socket: "); + label.setHorizontalAlignment(JLabel.RIGHT); + c.gridx = 0; + c.gridy++; + c.anchor = GridBagConstraints.EAST; + connectionInfoPanel.add(label); + + moteToSocketLabel = new JLabel("0 bytes"); + c.gridx++; + c.anchor = GridBagConstraints.WEST; + connectionInfoPanel.add(moteToSocketLabel); + + add(BorderLayout.CENTER, connectionInfoPanel); + + // --- Status bar + + JPanel statusBarPanel = new JPanel(new BorderLayout()) { + @Override + public Dimension getPreferredSize() { + Dimension d = super.getPreferredSize(); + return new Dimension(STATUSBAR_WIDTH, d.height); + } + }; + statusBarPanel.setLayout(new BoxLayout(statusBarPanel, BoxLayout.LINE_AXIS)); + statusBarPanel.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED)); + label = new JLabel("Status: "); + statusBarPanel.add(label); + + socketStatusLabel = new JLabel("Disconnected"); + socketStatusLabel.setForeground(Color.DARK_GRAY); + statusBarPanel.add(socketStatusLabel); + + add(BorderLayout.SOUTH, statusBarPanel); + + /* Mote serial port */ + serialPort = (SerialPort) mote.getInterfaces().getLog(); + if (serialPort == null) { + throw new RuntimeException("No mote serial port"); + } + + serverSelectButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("Connect")) { + try { + serverPortField.commitEdit(); + startClient(serverHostField.getText(), ((Long) serverPortField.getValue()).intValue()); + } catch (ParseException ex) { + logger.error(ex); + } + } else { + // close socket + cleanup(); + } + } + }); + + + /* Observe serial port for outgoing data and write to socket */ + serialPort.addSerialDataObserver(serialDataObserver = new Observer() { + @Override + public void update(Observable obs, Object obj) { + try { + if (out == null) { + return; + } + out.write(serialPort.getLastSerialData()); + out.flush(); + outBytes++; + if (Cooja.isVisualized()) { + moteToSocketLabel.setText(outBytes + " bytes"); + } + } catch (IOException ex) { + logger.error(ex.getMessage()); + socketStatusLabel.setText("failed"); + socketStatusLabel.setForeground(ST_COLOR_FAILED); + } + } + }); + } + + if (Cooja.isVisualized()) { + addClientListener(new ClientListener() { + + @Override + public void onError(final String msg) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + socketStatusLabel.setForeground(ST_COLOR_FAILED); + socketStatusLabel.setText(msg); + } + }); + } + + @Override + public void onConnected() { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + socketStatusLabel.setText("Connected"); + socketStatusLabel.setForeground(ST_COLOR_CONNECTED); + serverHostField.setEnabled(false); + serverPortField.setEnabled(false); + serverSelectButton.setText("Disconnect"); + } + }); + } + + @Override + public void onDisconnected() { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + socketStatusLabel.setForeground(ST_COLOR_UNCONNECTED); + socketStatusLabel.setText("Disconnected"); + serverHostField.setEnabled(true); + serverPortField.setEnabled(true); + serverSelectButton.setText("Connect"); + } + }); + } + }); + } + pack(); + } + + private List listeners = new LinkedList<>(); + + public interface ClientListener { + void onError(String msg); + void onConnected(); + void onDisconnected(); + } + + public void addClientListener(ClientListener listener) { + if (!listeners.contains(listener)) { + listeners.add(listener); + } + } + + private void notifyClientError(String msg) { + for (ClientListener l : listeners) { + l.onError(msg); + } + } + + private void notifyClientConnected() { + for (ClientListener l : listeners) { + l.onConnected(); + } + } + + private void notifyClientDisconnected() { + for (ClientListener l : listeners) { + l.onDisconnected(); + } + } + + + public void startClient(String host, int port) { + if (socket == null) { + // connect to serer + try { + logger.info("Connecting: " + host + ":" + port); + socket = new Socket(host, port); + in = new DataInputStream(socket.getInputStream()); + out = new DataOutputStream(socket.getOutputStream()); + out.flush(); + startSocketReadThread(in); + notifyClientConnected(); + } catch (IOException ex) { + logger.error(ex.getMessage()); + notifyClientError(ex.getMessage()); + } + } else { + // disconnect from server + try { + logger.info("Closing connection to serer..."); + socket.close(); + notifyClientDisconnected(); + } catch (IOException ex) { + logger.error(ex); + notifyClientError(ex.getMessage()); + } finally { + socket = null; + } + } + + } + + private void startSocketReadThread(final DataInputStream in) { + /* Forward data: virtual port -> mote */ + Thread incomingDataThread = new Thread(new Runnable() { + @Override + public void run() { + int numRead = 0; + byte[] data = new byte[1024]; + logger.info("Start forwarding: socket -> serial port"); + while (numRead >= 0) { + try { + numRead = in.read(data); + } catch (IOException e) { + logger.info(e.getMessage()); + numRead = -1; + continue; + } + + if (numRead >= 0) { + final int finalNumRead = numRead; + final byte[] finalData = data; + /* We are not on the simulation thread */ + simulation.invokeSimulationThread(new Runnable() { + + @Override + public void run() { + for (int i = 0; i < finalNumRead; i++) { + serialPort.writeByte(finalData[i]); + } + } + }); + + inBytes += numRead; + if (Cooja.isVisualized()) { + socketToMoteLabel.setText(inBytes + " bytes"); + } + } + } + + logger.info("Incoming data thread shut down"); + cleanup(); + notifyClientDisconnected(); + } + }); + incomingDataThread.start(); + } + + @Override + public Collection getConfigXML() { + List config = new ArrayList<>(); + Element element; + + // XXX isVisualized guards? + element = new Element("host"); + if (socket == null || !socket.isBound()) { + element.setText(serverHostField.getText()); + } else { + element.setText(socket.getInetAddress().getHostName()); + } + config.add(element); + + element = new Element("port"); + if (socket == null || !socket.isBound()) { + try { + serverPortField.commitEdit(); + element.setText(String.valueOf((Long) serverPortField.getValue())); + } catch (ParseException ex) { + logger.error(ex.getMessage()); + serverPortField.setText("null"); + } + } else { + element.setText(String.valueOf(socket.getPort())); + } + config.add(element); + + element = new Element("bound"); + if (socket == null) { + element.setText(String.valueOf(false)); + } else { + element.setText(String.valueOf(socket.isBound())); + } + config.add(element); + + return config; + } + + @Override + public boolean setConfigXML(Collection configXML, boolean visAvailable) { + String host = null; + Integer port = null; + boolean bound = false; + + for (Element element : configXML) { + switch (element.getName()) { + case "host": + host = element.getText(); + break; + case "port": + port = Integer.parseInt(element.getText()); + break; + case "bound": + bound = Boolean.parseBoolean(element.getText()); + break; + default: + logger.warn("Unknwon config element: " + element.getName()); + break; + } + } + + // XXX binding might fail if server not configured yet + if (Cooja.isVisualized()) { + if (host != null) { + serverHostField.setText(host); + } + if (port != null) { + serverPortField.setText(String.valueOf(port)); + } + if (bound) { + serverSelectButton.doClick(); + } + } else { + // if bound and all set up, start client + if (host != null && port != null) { + startClient(host, port); + } else { + logger.error("Client not started due to incomplete configuration"); + } + } + + return true; + } + + + private void cleanup() { + serialPort.deleteSerialDataObserver(serialDataObserver); + + try { + if (socket != null) { + socket.close(); + socket = null; + } + } catch (IOException e1) { + logger.warn(e1.getMessage()); + } + try { + if (in != null) { + in.close(); + in = null; + } + } catch (IOException e) { + logger.warn(e.getMessage()); + } + try { + if (out != null) { + out.close(); + out = null; + } + } catch (IOException e) { + logger.warn(e.getMessage()); + } + } + + @Override + public void closePlugin() { + cleanup(); + } + + @Override + public Mote getMote() { + return mote; + } +} + diff --git a/tools/cooja/apps/serial_socket/java/org/contikios/cooja/serialsocket/SerialSocketServer.java b/tools/cooja/apps/serial_socket/java/org/contikios/cooja/serialsocket/SerialSocketServer.java new file mode 100644 index 000000000..fa55da74b --- /dev/null +++ b/tools/cooja/apps/serial_socket/java/org/contikios/cooja/serialsocket/SerialSocketServer.java @@ -0,0 +1,657 @@ +package org.contikios.cooja.serialsocket; + +/* + * Copyright (c) 2014, TU Braunschweig. + * Copyright (c) 2010, 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.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.text.NumberFormat; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Observable; +import java.util.Observer; +import java.util.logging.Level; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JFormattedTextField; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSeparator; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.border.EtchedBorder; +import javax.swing.text.NumberFormatter; + +import org.apache.log4j.Logger; +import org.jdom.Element; + +import org.contikios.cooja.ClassDescription; +import org.contikios.cooja.Cooja; +import org.contikios.cooja.Mote; +import org.contikios.cooja.MotePlugin; +import org.contikios.cooja.PluginType; +import org.contikios.cooja.Simulation; +import org.contikios.cooja.VisPlugin; +import org.contikios.cooja.interfaces.SerialPort; + +/** + * Socket to simulated serial port forwarder. Server version. + * + * @author Fredrik Osterlind + * @author Enrico Jorns + */ +@ClassDescription("Serial Socket (SERVER)") +@PluginType(PluginType.MOTE_PLUGIN) +public class SerialSocketServer extends VisPlugin implements MotePlugin { + private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(SerialSocketServer.class); + + private final static int STATUSBAR_WIDTH = 350; + + private static final Color COLOR_NEUTRAL = Color.DARK_GRAY; + private static final Color COLOR_POSITIVE = new Color(0, 161, 83); + private static final Color COLOR_NEGATIVE = Color.RED; + + private final int SERVER_DEFAULT_PORT; + + private final SerialPort serialPort; + private Observer serialDataObserver; + + private JLabel socketToMoteLabel; + private JLabel moteToSocketLabel; + private JLabel socketStatusLabel; + private JFormattedTextField listenPortField; + private JButton serverStartButton; + + private int inBytes = 0, outBytes = 0; + + private ServerSocket serverSocket; + private Socket clientSocket; + + private Mote mote; + private Simulation simulation; + + public SerialSocketServer(Mote mote, Simulation simulation, final Cooja gui) { + super("Serial Socket (SERVER) (" + mote + ")", gui, false); + this.mote = mote; + this.simulation = simulation; + + updateTimer.start(); + + SERVER_DEFAULT_PORT = 60000 + mote.getID(); + + /* GUI components */ + if (Cooja.isVisualized()) { + + setResizable(false); + setLayout(new BorderLayout()); + + // --- Server Port setup + + GridBagConstraints c = new GridBagConstraints(); + JPanel socketPanel = new JPanel(new GridBagLayout()); + socketPanel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); + + JLabel label = new JLabel("Listen port: "); + c.gridx = 0; + c.gridy = 0; + c.weightx = 0.1; + c.anchor = GridBagConstraints.EAST; + socketPanel.add(label, c); + + NumberFormat nf = NumberFormat.getIntegerInstance(); + nf.setGroupingUsed(false); + listenPortField = new JFormattedTextField(new NumberFormatter(nf)); + listenPortField.setColumns(5); + listenPortField.setText(String.valueOf(SERVER_DEFAULT_PORT)); + c.gridx++; + c.weightx = 0.0; + socketPanel.add(listenPortField, c); + + serverStartButton = new JButton("Start") { // Button for label toggeling + private final String altString = "Stop"; + + @Override + public Dimension getPreferredSize() { + String origText = getText(); + Dimension origDim = super.getPreferredSize(); + setText(altString); + Dimension altDim = super.getPreferredSize(); + setText(origText); + return new Dimension(Math.max(origDim.width, altDim.width), origDim.height); + } + }; + c.gridx++; + c.weightx = 0.1; + c.anchor = GridBagConstraints.EAST; + socketPanel.add(serverStartButton, c); + + c.gridx = 0; + c.gridy++; + c.gridwidth = GridBagConstraints.REMAINDER; + c.fill = GridBagConstraints.HORIZONTAL; + socketPanel.add(new JSeparator(JSeparator.HORIZONTAL), c); + + add(BorderLayout.NORTH, socketPanel); + + // --- Incoming / outgoing info + + JPanel connectionInfoPanel = new JPanel(new GridLayout(0, 2)); + connectionInfoPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + c = new GridBagConstraints(); + + label = new JLabel("socket -> mote: "); + label.setHorizontalAlignment(JLabel.RIGHT); + c.gridx = 0; + c.gridy = 0; + c.anchor = GridBagConstraints.EAST; + connectionInfoPanel.add(label); + + socketToMoteLabel = new JLabel("0 bytes"); + c.gridx++; + c.anchor = GridBagConstraints.WEST; + connectionInfoPanel.add(socketToMoteLabel); + + label = new JLabel("mote -> socket: "); + label.setHorizontalAlignment(JLabel.RIGHT); + c.gridx = 0; + c.gridy++; + c.anchor = GridBagConstraints.EAST; + connectionInfoPanel.add(label); + + moteToSocketLabel = new JLabel("0 bytes"); + c.gridx++; + c.anchor = GridBagConstraints.WEST; + connectionInfoPanel.add(moteToSocketLabel); + + add(BorderLayout.CENTER, connectionInfoPanel); + + // --- Status bar + + JPanel statusBarPanel = new JPanel(new BorderLayout()) { + @Override + public Dimension getPreferredSize() { + Dimension d = super.getPreferredSize(); + return new Dimension(STATUSBAR_WIDTH, d.height); + } + }; + statusBarPanel.setLayout(new BoxLayout(statusBarPanel, BoxLayout.LINE_AXIS)); + statusBarPanel.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED)); + label = new JLabel("Status: "); + statusBarPanel.add(label); + + socketStatusLabel = new JLabel("Idle"); + socketStatusLabel.setForeground(Color.DARK_GRAY); + statusBarPanel.add(socketStatusLabel); + + add(BorderLayout.SOUTH, statusBarPanel); + + serverStartButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("Start")) { + try { + listenPortField.commitEdit(); + } catch (ParseException ex) { + java.util.logging.Logger.getLogger(SerialSocketClient.class.getName()).log(Level.SEVERE, null, ex); + } + startServer(((Long) listenPortField.getValue()).intValue()); + } else { + stopServer(); + } + } + }); + + pack(); + } + + /* Mote serial port */ + serialPort = (SerialPort) mote.getInterfaces().getLog(); + if (serialPort == null) { + throw new RuntimeException("No mote serial port"); + } + + if (Cooja.isVisualized()) { + // gui updates for server status updates + addServerListener(new ServerListener() { + + @Override + public void onServerStarted(final int port) { + SwingUtilities.invokeLater(new Runnable() { + + @Override + public void run() { + System.out.println("onServerStarted"); + socketStatusLabel.setForeground(COLOR_NEUTRAL); + socketStatusLabel.setText("Listening on port " + String.valueOf(port)); + listenPortField.setEnabled(false); + serverStartButton.setText("Stop"); + } + }); + } + + @Override + public void onClientConnected(final Socket client) { + SwingUtilities.invokeLater(new Runnable() { + + @Override + public void run() { + socketStatusLabel.setForeground(COLOR_POSITIVE); + socketStatusLabel.setText("Client " + + client.getInetAddress() + ":" + client.getPort() + + " connected."); + } + }); + } + + @Override + public void onClientDisconnected() { + SwingUtilities.invokeLater(new Runnable() { + + @Override + public void run() { + // XXX check why needed + if (serverSocket != null) { + socketStatusLabel.setForeground(COLOR_NEUTRAL); + socketStatusLabel.setText("Listening on port " + String.valueOf(serverSocket.getLocalPort())); + } + } + }); + } + + @Override + public void onServerStopped() { + SwingUtilities.invokeLater(new Runnable() { + + @Override + public void run() { + listenPortField.setEnabled(true); + serverStartButton.setText("Start"); + socketStatusLabel.setForeground(COLOR_NEUTRAL); + socketStatusLabel.setText("Idle"); + } + }); + } + + @Override + public void onServerError(final String msg) { + SwingUtilities.invokeLater(new Runnable() { + + @Override + public void run() { + socketStatusLabel.setForeground(COLOR_NEGATIVE); + socketStatusLabel.setText(msg); + } + }); + } + + }); + } + + } + + private List listeners = new LinkedList<>(); + + public interface ServerListener { + void onServerStarted(int port); + void onClientConnected(Socket client); + void onClientDisconnected(); + void onServerStopped(); + void onServerError(String msg); + } + + private void addServerListener(ServerListener listener) { + listeners.add(listener); + } + + public void notifyServerStarted(int port) { + for (ServerListener listener : listeners) { + listener.onServerStarted(port); + } + } + + public void notifyClientConnected(Socket client) { + for (ServerListener listener : listeners) { + listener.onClientConnected(client); + } + } + + public void notifyClientDisconnected() { + for (ServerListener listener : listeners) { + listener.onClientDisconnected(); + } + } + + public void notifyServerStopped() { + for (ServerListener listener : listeners) { + listener.onServerStopped(); + } + } + + public void notifyServerError(String msg) { + for (ServerListener listener : listeners) { + listener.onServerError(msg); + } + } + + /** + * Start server .. + * @param port + */ + public void startServer(int port) { + try { + serverSocket = new ServerSocket(port); + logger.info("Listening on port: " + port); + notifyServerStarted(port); + } catch (IOException ex) { + logger.error(ex.getMessage()); + notifyServerError(ex.getMessage()); + return; + } + + new Thread() { + private Thread incomingDataHandler; + @Override + public void run() { + while (!serverSocket.isClosed()) { + try { + // wait for next client + Socket candidateSocket = serverSocket.accept(); + + // reject connection if already one client connected + if (clientSocket != null && !clientSocket.isClosed()) { + logger.info("Refused connection of client " + candidateSocket.getInetAddress()); + candidateSocket.close(); + continue; + } + + clientSocket = candidateSocket; + + /* Start handler for data input from socket */ + incomingDataHandler = new Thread(new IncomingDataHandler()); + incomingDataHandler.start(); + + /* Observe serial port for outgoing data */ + serialDataObserver = new SerialDataObserver(); + serialPort.addSerialDataObserver(serialDataObserver); + + inBytes = outBytes = 0; + + logger.info("Client connected: " + clientSocket.getInetAddress()); + notifyClientConnected(clientSocket); + + } catch (IOException e) { + logger.info("Listening thread shut down: " + e.getMessage()); + try { + serverSocket.close(); + } catch (IOException ex) { + logger.error(ex); + } + } + } + cleanupClient(); + if (incomingDataHandler != null) { + // Wait for reader thread to terminate + try { + incomingDataHandler.join(500); + } catch (InterruptedException ex) { + logger.warn(ex); + } + } + notifyServerStopped(); + } + }.start(); + } + + /** + * Stops server by closing server listen socket. + */ + public void stopServer() { + try { + serverSocket.close(); + } catch (IOException ex) { + logger.error(ex); + } + } + + /* Forward data: virtual port -> mote */ + private class IncomingDataHandler implements Runnable { + + DataInputStream in; + + @Override + public void run() { + int numRead = 0; + byte[] data = new byte[1024]; + try { + in = new DataInputStream(clientSocket.getInputStream()); + } catch (IOException ex) { + logger.error(ex); + return; + } + + logger.info("Forwarder: socket -> serial port"); + while (numRead >= 0) { + final int finalNumRead = numRead; + final byte[] finalData = data; + /* We are not on the simulation thread */ + simulation.invokeSimulationThread(new Runnable() { + + @Override + public void run() { + for (int i = 0; i < finalNumRead; i++) { + serialPort.writeByte(finalData[i]); + } + inBytes += finalNumRead; + } + }); + + try { + numRead = in.read(data); + } catch (IOException e) { + logger.info(e.getMessage()); + numRead = -1; + } + } + logger.info("End of Stream"); + cleanupClient(); + } + } + + private class SerialDataObserver implements Observer { + + DataOutputStream out; + + public SerialDataObserver() { + try { + out = new DataOutputStream(clientSocket.getOutputStream()); + } catch (IOException ex) { + logger.error(ex); + out = null; + } + } + + @Override + public void update(Observable obs, Object obj) { + try { + if (out == null) { + /*logger.debug("out is null");*/ + return; + } + + out.write(serialPort.getLastSerialData()); + out.flush(); + + outBytes++; + } catch (IOException ex) { + logger.error(ex); + cleanupClient(); + } + } + + } + + @Override + public Collection getConfigXML() { + List config = new ArrayList<>(); + Element element; + + // XXX isVisualized guards? + + element = new Element("port"); + if (serverSocket == null || !serverSocket.isBound()) { + try { + listenPortField.commitEdit(); + element.setText(String.valueOf((Long) listenPortField.getValue())); + } catch (ParseException ex) { + logger.error(ex.getMessage()); + listenPortField.setText("null"); + } + } else { + element.setText(String.valueOf(serverSocket.getLocalPort())); + } + config.add(element); + + element = new Element("bound"); + if (serverSocket == null) { + element.setText(String.valueOf(false)); + } else { + element.setText(String.valueOf(!serverSocket.isClosed())); + } + config.add(element); + + return config; + } + + @Override + public boolean setConfigXML(Collection configXML, boolean visAvailable) { + Integer port = null; + boolean bound = false; + + for (Element element : configXML) { + switch (element.getName()) { + case "port": + port = Integer.parseInt(element.getText()); + break; + case "bound": + bound = Boolean.parseBoolean(element.getText()); + break; + default: + logger.warn("Unknwon config element: " + element.getName()); + break; + } + } + if (Cooja.isVisualized()) { + if (port != null) { + listenPortField.setText(String.valueOf(port)); + } + if (bound) { + serverStartButton.doClick(); + } + } else { + // if bound and all set up, start client + if (port != null) { + startServer(port); + } else { + logger.error("Server not started due to incomplete configuration"); + } + } + + return true; + } + + private void cleanupClient() { + try { + if (clientSocket != null) { + clientSocket.close(); + clientSocket = null; + } + } catch (IOException e1) { + logger.error(e1.getMessage()); + } + + serialPort.deleteSerialDataObserver(serialDataObserver); + + notifyClientDisconnected(); + } + + private boolean closed = false; + + @Override + public void closePlugin() { + closed = true; + cleanupClient(); + try { + if (serverSocket != null) { + serverSocket.close(); + } + } catch (IOException ex) { + logger.error(ex); + } + } + + @Override + public Mote getMote() { + return mote; + } + + private static final int UPDATE_INTERVAL = 150; + private Timer updateTimer = new Timer(UPDATE_INTERVAL, new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (closed) { + updateTimer.stop(); + return; + } + + socketToMoteLabel.setText(inBytes + " bytes"); + moteToSocketLabel.setText(outBytes + " bytes"); + } + }); +} +