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
|
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