summaryrefslogtreecommitdiff
path: root/tags/host/0.8.1/src/org/reprap/comms/snap/SNAPCommunicator.java
blob: 15c8fce23809100d0126b99473b097f2aa103cd5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
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.Preferences;
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;

/**
 *
 */
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;
	
	/**
	 * Boolean to turn debugging on or off on SNAP messages
	 * True indicates additional debugging messages will be printed
	 * Managed in the properties file @link{Preferences.loadGlobalBool("CommsDebug")}
	 */
	private boolean debugMode;
	
	
	/**
	 * 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, int baudRate, Address localAddress)
			throws NoSuchPortException, PortInUseException, IOException, UnsupportedCommOperationException {
		this.localAddress = localAddress;
		System.out.println("Opening port "+portName);
		CommPortIdentifier commId = CommPortIdentifier.getPortIdentifier(portName);
		port = (SerialPort)commId.open(portName, 30000);
		
		
		// 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();
		
		try {
			// Try to load debug setting from properties file
			debugMode = Preferences.loadGlobalBool("CommsDebug");
		} catch (Exception ex) {
			// Fall back to non-debug mode if no setting is available
			debugMode = false;
		}
	}
	
	public void close()
	{
		if (port != null)
			port.close();
		port = null;
	}
	
	private void dumpPacket(Device device, OutgoingMessage messageToSend) {
		byte [] binaryMessage = messageToSend.getBinary(); 
		System.out.print(localAddress.toString());
		System.out.print("->");
		System.out.print(device.getAddress().toString());
		System.out.print(": ");
		for(int i = 0; i < binaryMessage.length; i++)
			System.out.print(Integer.toHexString(binaryMessage[i]>=0?binaryMessage[i]:binaryMessage[i]+256) + " ");
		System.out.println("");
	}
	
	public IncomingContext sendMessage(Device device,
			OutgoingMessage messageToSend) throws IOException {
		
		byte [] binaryMessage = messageToSend.getBinary(); 
		SNAPPacket packet = new SNAPPacket((SNAPAddress)localAddress,
				(SNAPAddress)device.getAddress(),
				binaryMessage);

		for(;;) 
		{
			if (debugMode) 
			{
				System.out.print("TX ");
				dumpPacket(device, messageToSend);
			}

			sendRawMessage(packet);

			SNAPPacket ackPacket;
			try {
				ackPacket = receivePacket(ackTimeout);	
			} catch (IOException ex) {
				// An error occurred during receive, so send and try again
				//if (debugMode) {
					System.out.println("Receive error, re-sending: " + ex.getMessage());
					dumpPacket(device, messageToSend);
				//}
				continue;
			}
			if (ackPacket.isAck())
				break;
			if (ackPacket.getSourceAddress().equals(localAddress)) {
				// Packet was from us, so assume no node present
				System.out.println("Device at address " + device.getAddress() + " not present");
				throw new IOException("Device at address " + device.getAddress() + " not present");
			}
			if (!ackPacket.isNak()) {
				System.out.println("Received data packet when expecting ACK");
			}
			
			// All gone wrong - wait a bit and try again - ***AB
			System.out.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;
		if (debugMode) System.out.print("RX ");
		try {
			port.enableReceiveTimeout(messageTimeout);
		} catch (UnsupportedCommOperationException e) {
			System.out.println("Read timeouts unsupported on this platform");
		}
		for(;;) {
			int c = readByte(timeout);
			if (debugMode) System.out.print(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
					continue;
				packet = new SNAPPacket();
			}
			if (packet.receiveByte((byte)c)) {
				// Packet is complete
				if (packet.validate()) {
					if (debugMode) System.out.println("");
					return packet;
				} else {
					System.out.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()) {
			System.out.println("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
			System.out.println("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. 

}