Merge pull request #781 from ejoerns/pull-req/cooja-serialsocket
[Cooja] Configurable serial socket plugin
This commit is contained in:
commit
8df1c98a85
|
@ -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
|
||||
|
|
|
@ -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<Element> configXML, boolean visAvailable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Collection<Element> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Element> configXML, boolean visAvailable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Collection<Element> 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");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -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<ClientListener> 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<Element> getConfigXML() {
|
||||
List<Element> 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<Element> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ServerListener> 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<Element> getConfigXML() {
|
||||
List<Element> 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<Element> 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");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in a new issue