package org.reprap.comms.snap;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.UnsupportedCommOperationException;
import org.reprap.Device;
import org.reprap.utilities.Debug;
import org.reprap.comms.Address;
import org.reprap.comms.Communicator;
import org.reprap.comms.IncomingContext;
import org.reprap.comms.IncomingMessage;
import org.reprap.comms.OutgoingMessage;
import org.reprap.Preferences;
/**
*
*/
public class SNAPCommunicator implements Communicator {
/**
* Timeout in milliseconds before a timeout exception is thrown
* when waiting for an ACK from a device
*/
private final static int ackTimeout = 300;
/**
*
*/
private final static int messageTimeout = 300;
/**
*
*/
private Address localAddress;
/**
* Serial port used for comms
* Controlled via the properties (@link)
*/
private SerialPort port;
/**
*
*/
private OutputStream writeStream;
/**
*
*/
private InputStream readStream;
//private ReceiveThread receiveThread = null;
/**
* Lock for comms
* @link CommsLock
*/
private CommsLock lock = new CommsLock();
/**
* Construct a new SNAP communicator
* @param portName port used for comms
* @param baudRate Speeds of communication (set via properties @link??)
* @param localAddress
* @throws NoSuchPortException exception thrown when the port does not exist @see
* @throws PortInUseException exception thrown when the port is in use @see
* @throws IOException
* @throws UnsupportedCommOperationException
*/
public SNAPCommunicator(String portName, Address localAddress)
throws NoSuchPortException, PortInUseException, IOException, UnsupportedCommOperationException {
this.localAddress = localAddress;
Debug.d("Opening port " + portName);
CommPortIdentifier commId = CommPortIdentifier.getPortIdentifier(portName);
port = (SerialPort)commId.open(portName, 30000);
int baudRate = Preferences.loadGlobalInt("BaudRate");
// Workround for javax.comm bug.
// See http://forum.java.sun.com/thread.jspa?threadID=673793
// FIXME: jvandewiel: is this workaround also needed when using the RXTX library?
try {
port.setSerialPortParams(baudRate,
SerialPort.DATABITS_8,
SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
}
catch (Exception e) {
}
port.setSerialPortParams(baudRate,
SerialPort.DATABITS_8,
SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
// End of workround
try {
port.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
} catch (Exception e) {
// Um, Linux USB ports don't do this. What can I do about it?
}
writeStream = port.getOutputStream();
readStream = port.getInputStream();
Debug.d("Attempting to initialize Arduino");
try {Thread.sleep(2000);} catch (Exception e) {}
for(int i = 0; i < 10; i++)
writeStream.write('0');
try {Thread.sleep(1000);} catch (Exception e) {}
}
public void close()
{
if (port != null)
port.close();
port = null;
}
private String dumpPacket(Device device, OutgoingMessage messageToSend) {
byte [] binaryMessage = messageToSend.getBinary();
String r = localAddress.toString();
r += "->";
r += device.getAddress().toString();
r += ": ";
String rDec = " ( = ";
for(int i = 0; i < binaryMessage.length; i++)
{
r += Integer.toHexString(binaryMessage[i]>=0?binaryMessage[i]:binaryMessage[i]+256) + " ";
rDec += Integer.toString(binaryMessage[i]>=0?binaryMessage[i]:binaryMessage[i]+256) + " ";
}
return r + rDec + ")";
}
public IncomingContext sendMessage(Device device,
OutgoingMessage messageToSend) throws IOException {
byte [] binaryMessage = messageToSend.getBinary();
SNAPPacket packet = new SNAPPacket((SNAPAddress)localAddress,
(SNAPAddress)device.getAddress(),
binaryMessage);
int tryCount = 0;
for(;;)
{
Debug.c("tx " + dumpPacket(device, messageToSend));
sendRawMessage(packet);
SNAPPacket ackPacket;
ackPacket = null;
try {
ackPacket = receivePacket(ackTimeout);
} catch (IOException ex) {
tryCount++;
Debug.d("Receive error, re-sending: " + ex.getMessage() + "; try: " + tryCount + "; " +
dumpPacket(device, messageToSend));
if(tryCount < 16)
continue;
else
ackPacket = null;
}
if(ackPacket == null)
throw new IOException("Resend count exceeded.");
if (ackPacket.isAck())
break;
if (ackPacket.getSourceAddress().equals(localAddress)) {
// Packet was from us, so assume no node present
Debug.d("Device at address " + device.getAddress() + " not present");
throw new IOException("Device at address " + device.getAddress() + " not present");
}
if (!ackPacket.isNak()) {
System.err.println("Received data packet when expecting ACK");
}
// All gone wrong - wait a bit and try again - ***AB
System.err.println("sendMessage error - retrying");
try
{
Thread.sleep(100);
} catch (Exception e)
{
}
}
IncomingContext replyContext = messageToSend.getReplyContext(this,
device);
return replyContext;
}
private synchronized void sendRawMessage(SNAPPacket packet) throws IOException {
// try{
// Thread.sleep(200);
// } catch (Exception ex)
// {
// System.err.println("Comms sleep: " + ex.toString());
// }
writeStream.write(packet.getRawData());
}
private int readByte(long timeout) throws IOException {
long t0 = System.currentTimeMillis();
int c = -1;
// Sometimes javacomm seems to freak out and say something
// timed out when it didn't, so double check and try again
// if it really didn't time out
for(;;) {
c = readStream.read();
if (c != -1)
return c;
if (System.currentTimeMillis() - t0 >= timeout)
return -1;
try {
// Just to avoid a deadly spin if something unexpected happens
Thread.sleep(1);
} catch (InterruptedException e) {
}
}
}
protected synchronized SNAPPacket receivePacket(long timeout) throws IOException {
SNAPPacket packet = null;
try {
port.enableReceiveTimeout(messageTimeout);
} catch (UnsupportedCommOperationException e) {
Debug.d("Read timeouts unsupported on this platform");
}
String debugMsg = "debug";
String msgHex = "rx: ";
String msgDec = " ( = ";
Boolean debug = false;
for(;;) {
int c = readByte(timeout);
if (debug == true)
{
if (c == '\n')
{
Debug.c(debugMsg);
debug = false;
}
else
debugMsg += (char)c;
}
else
{
msgDec += Integer.toString(c) + " ";
msgHex += Integer.toHexString(c) + " ";
if (c == -1)
throw new IOException("Timeout receiving byte");
if (packet == null) {
if (c != 0x54) // Always wait for a sync byte before doing anything
{
if (c == 'd')
{
debug = true;
debugMsg = "From firmware: ";
msgHex = "rx: ";
msgDec = " ( = ";
}
continue;
}
else
packet = new SNAPPacket();
}
if (packet.receiveByte((byte)c)) {
// Packet is complete
if (packet.validate()) {
Debug.c(msgHex + msgDec + ")");
return packet;
} else {
System.err.println("CRC error");
throw new IOException("CRC error");
}
}
}
}
}
public void receiveMessage(IncomingMessage message) throws IOException {
receiveMessage(message, messageTimeout);
}
public void receiveMessage(IncomingMessage message, long timeout) throws IOException {
// Here we collect one packet and notify the message
// of its contents. The message will respond
// to indicate if it wants the message. If not,
// it will be discarded and we will wait for another
// message.
// Since this is a SNAP ring, we have to pass on
// any packets that are not destined for us.
// We will also only pass packets to the message if they are for
// the local address.
for(;;) {
SNAPPacket packet = receivePacket(timeout);
if (processPacket(message, packet))
return;
}
}
private boolean processPacket(IncomingMessage message, SNAPPacket packet) throws IOException {
// First ACK the message
if (packet.isAck()) {
Debug.d("Unexpected ACK received instead of message, not supported yet");
return false;
}
/// TODO send ACKs
//sendRawMessage(packet.generateACK());
if (!packet.getDestinationAddress().equals(localAddress)) {
// Not for us, so forward it on
sendRawMessage(packet);
return false;
} else if (message.receiveData(packet.getPayload())) {
// All received as expected
return true;
} else {
// Not interested, wait for more
Debug.d("Ignored and dropped packet");
return false;
}
}
public Address getAddress() {
return localAddress;
}
public void dispose() {
close();
}
public void lock() {
lock.lock();
}
public void unlock() {
lock.unlock();
}
// TODO make a background receiver thread. It can keep a pool of async receive contexts and
// fire them off if anything matching arrives.
// TODO Make a generic message receiver. Use reflection to get correct class.
}