Commit 91cb3ce7 authored by akwizgran's avatar akwizgran

Modem implementation using jSSC (untested).

parent 7f1b01be
package net.sf.briar.plugins.modem;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
interface Modem {
/**
* Call this method once after creating the modem. If an exception is
* thrown, the modem cannot be used.
*/
void init() throws IOException;
/**
* Initiates an outgoing call and returns true if the call connects. If the
* call does not connect the modem is hung up.
*/
boolean dial(String number) throws IOException;
/** Returns a stream for reading from the currently connected call. */
InputStream getInputStream();
/** Returns a stream for writing to the currently connected call. */
OutputStream getOutputStream();
/** Hangs up the modem, ending the currently connected call. */
void hangUp() throws IOException;
interface Callback {
/** Called when an incoming call connects. */
void incomingCallConnected();
}
}
package net.sf.briar.plugins.modem;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static jssc.SerialPort.PURGE_RXCLEAR;
import static jssc.SerialPort.PURGE_TXCLEAR;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import jssc.SerialPort;
import jssc.SerialPortEvent;
import jssc.SerialPortEventListener;
import jssc.SerialPortException;
class ModemImpl implements Modem, SerialPortEventListener {
private static final Logger LOG =
Logger.getLogger(ModemImpl.class.getName());
private static final int MAX_LINE_LENGTH = 256;
private static final int[] BAUD_RATES = {
256000, 128000, 115200, 57600, 38400, 19200, 14400, 9600, 4800, 1200
};
private static final int OK_TIMEOUT = 5 * 1000; // Milliseconds
private static final int CONNECT_TIMEOUT = 60 * 1000; // Milliseconds
private final Callback callback;
private final SerialPort port;
private final AtomicBoolean initialised, offHook, connected;
private final byte[] line;
private int lineLen = 0;
// A fresh queue is used for each connection
private volatile BlockingQueue<byte[]> received;
ModemImpl(Callback callback, String portName) {
this.callback = callback;
port = new SerialPort(portName);
initialised = new AtomicBoolean(false);
offHook = new AtomicBoolean(false);
connected = new AtomicBoolean(false);
line = new byte[MAX_LINE_LENGTH];
received = new LinkedBlockingQueue<byte[]>();
}
public void init() throws IOException {
if(LOG.isLoggable(INFO)) LOG.info("Initialising");
try {
// Open the serial port
if(!port.openPort())
throw new IOException("Failed to open serial port");
// Find a suitable baud rate
boolean foundBaudRate = false;
for(int baudRate : BAUD_RATES) {
if(port.setParams(baudRate, 8, 1, 0)) {
foundBaudRate = true;
break;
}
}
if(!foundBaudRate)
throw new IOException("Could not find a suitable baud rate");
// Listen for incoming data and hangup events
port.addEventListener(this);
// Initialise the modem
port.purgePort(PURGE_RXCLEAR | PURGE_TXCLEAR);
port.writeBytes("ATZ\r\n".getBytes("US-ASCII")); // Reset
port.writeBytes("ATE0\r\n".getBytes("US-ASCII")); // Echo off
} catch(SerialPortException e) {
throw new IOException(e.toString());
}
try {
// Wait for the modem to respond "OK"
synchronized(initialised) {
if(!initialised.get()) initialised.wait(OK_TIMEOUT);
if(!initialised.get())
throw new IOException("Modem did not respond");
}
} catch(InterruptedException e) {
if(LOG.isLoggable(WARNING))
LOG.warning("Interrupted while initialising modem");
Thread.currentThread().interrupt();
throw new IOException("Interrupted while initialising modem");
}
}
public boolean dial(String number) throws IOException {
if(offHook.getAndSet(true)) {
if(LOG.isLoggable(INFO))
LOG.info("Not dialling - call in progress");
return false;
}
if(LOG.isLoggable(INFO)) LOG.info("Dialling");
try {
port.writeBytes(("ATDT" + number + "\r\n").getBytes("US-ASCII"));
} catch(SerialPortException e) {
throw new IOException(e.toString());
}
try {
synchronized(connected) {
if(!connected.get()) connected.wait(CONNECT_TIMEOUT);
}
} catch(InterruptedException e) {
if(LOG.isLoggable(WARNING))
LOG.warning("Interrupted while connecting outgoing call");
Thread.currentThread().interrupt();
throw new IOException("Interrupted while connecting outgoing call");
}
if(connected.get()) return true;
hangUp();
return false;
}
public InputStream getInputStream() {
return new ModemInputStream(received);
}
public OutputStream getOutputStream() {
return new ModemOutputStream();
}
public void hangUp() throws IOException {
if(LOG.isLoggable(INFO)) LOG.info("Hanging up");
try {
port.setDTR(false);
} catch(SerialPortException e) {
throw new IOException(e.toString());
}
received.add(new byte[0]); // Empty buffer indicates EOF
received = new LinkedBlockingQueue<byte[]>();
connected.set(false);
offHook.set(false);
}
public void serialEvent(SerialPortEvent ev) {
try {
if(ev.isRXCHAR()) {
byte[] b = port.readBytes();
if(connected.get()) received.add(b);
else handleText(b);
} else if(ev.isDSR() && ev.getEventValue() == 0) {
if(LOG.isLoggable(INFO)) LOG.info("Remote end hung up");
hangUp();
}
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
} catch(SerialPortException e) {
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
}
}
private void handleText(byte[] b) throws IOException {
if(lineLen + b.length > MAX_LINE_LENGTH)
throw new IOException("Line too long");
for(int i = 0; i < b.length; i++) {
line[lineLen] = b[i];
if(b[i] == '\n') {
String s = new String(line, 0, lineLen, "US-ASCII").trim();
lineLen = 0;
if(LOG.isLoggable(INFO)) LOG.info("Modem status: " + s);
if(s.startsWith("CONNECT")) {
// There might be data in the buffer as well as text
int off = i + 1;
if(off < b.length) {
byte[] data = new byte[b.length - off];
System.arraycopy(b, off, data, 0, data.length);
received.add(data);
}
synchronized(connected) {
if(!connected.getAndSet(true))
connected.notifyAll();
}
return;
} else if(s.equals("OK")) {
synchronized(initialised) {
if(!initialised.getAndSet(true))
initialised.notifyAll();
}
} else if(s.equals("RING")) {
// FIXME: Don't do this on the event thread
answer();
}
} else {
lineLen++;
}
}
}
private void answer() throws IOException {
if(offHook.getAndSet(true)) {
if(LOG.isLoggable(INFO))
LOG.info("Not answering - call in progress");
return;
}
if(LOG.isLoggable(INFO)) LOG.info("Answering");
try {
port.writeBytes("ATA\r\n".getBytes("US-ASCII"));
} catch(SerialPortException e) {
throw new IOException(e.toString());
}
try {
synchronized(connected) {
if(!connected.get()) connected.wait(CONNECT_TIMEOUT);
}
} catch(InterruptedException e) {
if(LOG.isLoggable(WARNING))
LOG.warning("Interrupted while connecting incoming call");
Thread.currentThread().interrupt();
throw new IOException("Interrupted while connecting incoming call");
}
if(connected.get()) callback.incomingCallConnected();
else hangUp();
}
private static class ModemInputStream extends InputStream {
private final BlockingQueue<byte[]> received;
private byte[] buf = null;
private int offset = 0;
private ModemInputStream(BlockingQueue<byte[]> received) {
this.received = received;
}
@Override
public int read() throws IOException {
getBufferIfNecessary();
if(buf.length == 0) return -1;
return buf[offset++];
}
@Override
public int read(byte[] b) throws IOException {
getBufferIfNecessary();
if(buf.length == 0) return -1;
int len = Math.min(b.length, buf.length - offset);
System.arraycopy(buf, offset, b, 0, len);
offset += len;
return len;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
getBufferIfNecessary();
if(buf.length == 0) return -1;
len = Math.min(len, buf.length - offset);
System.arraycopy(buf, offset, b, off, len);
offset += len;
return len;
}
private void getBufferIfNecessary() throws IOException {
if(buf == null || offset == buf.length) {
try {
buf = received.take();
} catch(InterruptedException e) {
if(LOG.isLoggable(WARNING))
LOG.warning("Interrupted while reading");
Thread.currentThread().interrupt();
throw new IOException(e.toString());
}
offset = 0;
}
}
}
private class ModemOutputStream extends OutputStream {
@Override
public void write(int b) throws IOException {
try {
port.writeByte((byte) b);
} catch(SerialPortException e) {
throw new IOException(e.toString());
}
}
@Override
public void write(byte[] b) throws IOException {
try {
port.writeBytes(b);
} catch(SerialPortException e) {
throw new IOException(e.toString());
}
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
if(len < b.length) {
byte[] copy = new byte[len];
System.arraycopy(b, off, copy, 0, len);
write(copy);
} else {
write(b);
}
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment