1 package org.lsst.ccs.drivers.ftdi;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.OutputStream;
6 import java.net.Socket;
7 import java.util.HashMap;
8 import java.util.Iterator;
9 import java.util.concurrent.ArrayBlockingQueue;
10 import org.lsst.ccs.utilities.conv.Convert;
11
12 /**
13 ***************************************************************************
14 **
15 ** Remotely accesses a device which uses the FTDI chip.
16 **
17 ** @author Owen Saxton
18 **
19 ***************************************************************************
20 */
21 public class FtdiClient implements FtdiInterface {
22
23 /**
24 ***************************************************************************
25 **
26 ** Private fields.
27 **
28 ***************************************************************************
29 */
30 private Socket srvSock;
31 private InputStream srvIn;
32 private OutputStream srvOut;
33 private HashMap<Integer, ArrayBlockingQueue> queueMap = new HashMap();
34 private ThreadLocal<Integer> threadId;
35 private ThreadLocal<ArrayBlockingQueue> threadQueue;
36 private int currThreadId = 0;
37
38
39 /**
40 ***************************************************************************
41 **
42 ** Inner class implementing a network reading thread.
43 **
44 ***************************************************************************
45 */
46 private class Reader extends Thread {
47
48 @Override
49 public void run()
50 {
51 FtdiException excp = null;
52 while (true) {
53 try {
54 byte[] header = new byte[FtdiServer.POSN_DATA];
55 int leng = 0, recLeng = header.length;
56 while (leng < recLeng) {
57 int nread = srvIn.read(header, leng, recLeng - leng);
58 if (nread < 0) break;
59 leng += nread;
60 }
61 if (leng < recLeng) {
62 throw new FtdiException("Server disconnected");
63 }
64 if (Convert.bytesToInt(header, FtdiServer.POSN_MAGIC)
65 != FtdiServer.MAGIC_NUMBER) {
66 System.out.print("Header =");
67 for (int j = 0; j < header.length; j++) {
68 System.out.format(" %02x", header[j]);
69 }
70 System.out.println();
71 throw new FtdiException("Invalid magic number");
72 }
73 recLeng = Convert.bytesToShort(header,
74 FtdiServer.POSN_LENGTH);
75 byte[] reply = new byte[recLeng];
76 System.arraycopy(header, 0, reply, 0, leng);
77 while (leng < recLeng) {
78 int nread = srvIn.read(reply, leng, recLeng - leng);
79 if (nread < 0) break;
80 leng += nread;
81 }
82 if (leng < recLeng) {
83 throw new FtdiException("Server disconnected");
84 }
85 int context = Convert.bytesToShort(reply,
86 FtdiServer.POSN_CONTEXT);
87 queueMap.get(context).offer(reply);
88 }
89 catch (FtdiException e) {
90 excp = e;
91 closeNetSilent();
92 break;
93 }
94 catch (IOException e) {
95 if (closeNetSilent()) {
96 excp = new FtdiException(e);
97 }
98 break;
99 }
100 }
101 if (excp != null) {
102 Iterator queues = queueMap.values().iterator();
103 while (queues.hasNext()) {
104 ((ArrayBlockingQueue)queues.next()).offer(excp);
105 }
106 }
107 }
108
109 }
110
111
112 /**
113 ***************************************************************************
114 **
115 ** Opens a local device.
116 **
117 ** This is an invalid operation.
118 **
119 ** @param index The zero-based index of the FTDI device within the
120 ** list selected by the serial argument.
121 **
122 ** @param serial A string which, if non-null and non-empty, restricts
123 ** the list of available devices to those with a serial
124 ** number containing this string.
125 **
126 ** @throws FtdiException
127 **
128 ***************************************************************************
129 */
130 @Override
131 public void open(int index, String serial) throws FtdiException
132 {
133 throw new FtdiException("Invalid local open call");
134 }
135
136
137 /**
138 ***************************************************************************
139 **
140 ** Opens a remote device.
141 **
142 ** @param node The name of the node where the device is located.
143 **
144 ** @param index The zero-based index of the FTDI device within the
145 ** list selected by the serial argument.
146 **
147 ** @param serial A string which, if non-null and non-empty, restricts
148 ** the list of available devices to those with a serial
149 ** number containing this string.
150 **
151 ** @throws FtdiException
152 **
153 ***************************************************************************
154 */
155 @Override
156 public void open(String node, int index, String serial)
157 throws FtdiException
158 {
159 try {
160 srvSock = new Socket(node, FtdiServer.SERVER_PORT);
161 srvIn = srvSock.getInputStream();
162 srvOut = srvSock.getOutputStream();
163 }
164 catch (IOException e) {
165 throw new FtdiException(e);
166 }
167 queueMap.clear();
168 threadId = new ThreadLocal();
169 threadQueue = new ThreadLocal();
170 Reader rdr = new Reader();
171 rdr.setDaemon(true);
172 rdr.start();
173 byte[] bSerial = serial == null ? new byte[0] : serial.getBytes();
174 int lSerial = bSerial.length;
175 byte[] rqst = new byte[FtdiServer.POSN_SERIAL + lSerial];
176 Convert.intToBytes(index, rqst, FtdiServer.POSN_INDEX);
177 System.arraycopy(bSerial, 0, rqst, FtdiServer.POSN_SERIAL, lSerial);
178 try {
179 send(FtdiServer.FUNC_OPEN, rqst);
180 receive();
181 }
182 catch (FtdiException e) {
183 closeNetSilent();
184 throw e;
185 }
186 }
187
188
189 /**
190 ***************************************************************************
191 **
192 ** Closes the device.
193 **
194 ** @throws FtdiException
195 **
196 ***************************************************************************
197 */
198 @Override
199 public void close() throws FtdiException
200 {
201 byte[] rqst = new byte[FtdiServer.POSN_DATA];
202 send(FtdiServer.FUNC_CLOSE, rqst);
203 closeNet();
204 }
205
206
207 /**
208 ***************************************************************************
209 **
210 ** Sets the baud rate.
211 **
212 ** @param baudrate The baud rate ro set
213 **
214 ** @throws FtdiException
215 **
216 ***************************************************************************
217 */
218 @Override
219 public void setBaudrate(int baudrate) throws FtdiException
220 {
221 byte[] rqst = new byte[FtdiServer.POSN_BAUDRATE + 4];
222 Convert.intToBytes(baudrate, rqst, FtdiServer.POSN_BAUDRATE);
223 send(FtdiServer.FUNC_BAUDRATE, rqst);
224 receive();
225 }
226
227
228 /**
229 ***************************************************************************
230 **
231 ** Sets the data characteristics.
232 **
233 ** @param wordLength The encoded word length to set
234 **
235 ** @param stopBits The encoded number of stop bits to set
236 **
237 ** @param parity The encoded parity value to set
238 **
239 ** @throws FtdiException
240 **
241 ***************************************************************************
242 */
243 @Override
244 public void setDataCharacteristics(int wordLength, int stopBits,
245 int parity)
246 throws FtdiException
247 {
248 byte[] rqst = new byte[FtdiServer.POSN_PARITY + 4];
249 Convert.intToBytes(wordLength, rqst, FtdiServer.POSN_WORDLENG);
250 Convert.intToBytes(stopBits, rqst, FtdiServer.POSN_STOPBITS);
251 Convert.intToBytes(parity, rqst, FtdiServer.POSN_PARITY);
252 send(FtdiServer.FUNC_DATACHAR, rqst);
253 receive();
254 }
255
256
257 /**
258 ***************************************************************************
259 **
260 ** Sets the timeouts.
261 **
262 ** @param rcveTimeout The receive timeout to set (ms)
263 **
264 ** @param xmitTimeout The transmit timeout to set (ms)
265 **
266 ** @throws FtdiException
267 **
268 ***************************************************************************
269 */
270 @Override
271 public void setTimeouts(int rcveTimeout, int xmitTimeout)
272 throws FtdiException
273 {
274 byte[] rqst = new byte[FtdiServer.POSN_XMITTMO + 4];
275 Convert.intToBytes(rcveTimeout, rqst, FtdiServer.POSN_RCVETMO);
276 Convert.intToBytes(xmitTimeout, rqst, FtdiServer.POSN_XMITTMO);
277 send(FtdiServer.FUNC_TIMEOUTS, rqst);
278 receive();
279 }
280
281
282 /**
283 ***************************************************************************
284 **
285 ** Reads data.
286 **
287 ** Execution is blocked until either the byte array is filled, or a
288 ** timeout occurs. In the latter case the number of bytes read will be
289 ** less than the array size.
290 **
291 ** @param data A byte array to receive the read data.
292 **
293 ** @return The number of bytes read.
294 **
295 ** @throws FtdiException
296 **
297 ***************************************************************************
298 */
299 @Override
300 public int read(byte[] data) throws FtdiException
301 {
302 return read(data, 0, data.length);
303 }
304
305
306 /**
307 ***************************************************************************
308 **
309 ** Reads data.
310 **
311 ** Execution is blocked until either the requested number of bytes has
312 ** been read, or a timeout occurs. In the latter case the number of
313 ** bytes read will be less than the requested number.
314 **
315 ** @param data A byte array to receive the read data
316 **
317 ** @param offset The offset in the array to the start of the data
318 **
319 ** @param count The maximum number of bytes to read
320 **
321 ** @return The number of bytes read
322 **
323 ** @throws FtdiException
324 **
325 ***************************************************************************
326 */
327 @Override
328 public int read(byte[] data, int offset, int count) throws FtdiException
329 {
330 byte[] rqst = new byte[FtdiServer.POSN_READLENG + 4];
331 Convert.intToBytes(count, rqst, FtdiServer.POSN_READLENG);
332 send(FtdiServer.FUNC_READ, rqst);
333 byte[] reply = receive();
334 int nread = reply.length - FtdiServer.POSN_READDATA;
335 System.arraycopy(reply, FtdiServer.POSN_READDATA, data, offset, nread);
336
337 return nread;
338 }
339
340
341 /**
342 ***************************************************************************
343 **
344 ** Writes data.
345 **
346 ** @param data A byte array containing the data to write.
347 **
348 ** @return The number of bytes written.
349 **
350 ** @throws FtdiException
351 **
352 ***************************************************************************
353 */
354 @Override
355 public int write(byte[] data) throws FtdiException
356 {
357 return write(data, 0, data.length);
358 }
359
360
361 /**
362 ***************************************************************************
363 **
364 ** Writes data.
365 **
366 ** @param data A byte array containing the data to write
367 **
368 ** @param offset The offset in the array to the start of the data
369 **
370 ** @param count The number of bytes to write
371 **
372 ** @return The number of bytes written
373 **
374 ** @throws FtdiException
375 **
376 ***************************************************************************
377 */
378 @Override
379 public int write(byte[] data, int offset, int count) throws FtdiException
380 {
381 byte[] rqst = new byte[FtdiServer.POSN_WRITEDATA + count];
382 System.arraycopy(data, offset, rqst, FtdiServer.POSN_WRITEDATA, count);
383 send(FtdiServer.FUNC_WRITE, rqst);
384
385 return Convert.bytesToInt(receive(), FtdiServer.POSN_WRITELENG);
386 }
387
388
389 /**
390 ***************************************************************************
391 **
392 ** Gets the read queue status.
393 **
394 ** This is the number of bytes available for immediate read
395 **
396 ** @return The number of bytes in the read queue
397 **
398 ** @throws FtdiException
399 **
400 ***************************************************************************
401 */
402 @Override
403 public int getQueueStatus() throws FtdiException
404 {
405 byte[] rqst = new byte[FtdiServer.POSN_DATA];
406 send(FtdiServer.FUNC_QUEUESTAT, rqst);
407
408 return Convert.bytesToInt(receive(), FtdiServer.POSN_QUEUESTAT);
409 }
410
411
412 /**
413 ***************************************************************************
414 **
415 ** Gets the modem status.
416 **
417 ** @return The modem status as a set of bits
418 **
419 ** @throws FtdiException
420 **
421 ***************************************************************************
422 */
423 @Override
424 public int getModemStatus() throws FtdiException
425 {
426 byte[] rqst = new byte[FtdiServer.POSN_DATA];
427 send(FtdiServer.FUNC_MODEMSTAT, rqst);
428
429 return Convert.bytesToInt(receive(), FtdiServer.POSN_MODEMSTAT);
430 }
431
432
433 /**
434 ***************************************************************************
435 **
436 ** Sends a request to the server.
437 **
438 ** @param rqst A byte array containing the request.
439 **
440 ** @throws FtdiException
441 **
442 ***************************************************************************
443 */
444 private void send(int function, byte[] rqst) throws FtdiException
445 {
446 int context;
447 Integer id = threadId.get();
448 if (id == null) {
449 context = currThreadId++;
450 threadId.set(context);
451 threadQueue.set(new ArrayBlockingQueue(1));
452 queueMap.put(context, threadQueue.get());
453 }
454 else {
455 context = id;
456 }
457 Convert.intToBytes(FtdiServer.MAGIC_NUMBER, rqst,
458 FtdiServer.POSN_MAGIC);
459 Convert.shortToBytes((short)rqst.length, rqst, FtdiServer.POSN_LENGTH);
460 Convert.shortToBytes((short)function, rqst, FtdiServer.POSN_FUNCTION);
461 Convert.shortToBytes((short)context, rqst, FtdiServer.POSN_CONTEXT);
462 try {
463 srvOut.write(rqst);
464 }
465 catch (IOException e) {
466 throw new FtdiException(e);
467 }
468 }
469
470
471 /**
472 ***************************************************************************
473 **
474 ** Receives a reply from the server.
475 **
476 ** @return A byte array containing the reply.
477 **
478 ** @throws FtdiException
479 **
480 ***************************************************************************
481 */
482 private byte[] receive() throws FtdiException
483 {
484 Object replyObj = null;
485 try {
486 replyObj = threadQueue.get().take();
487 }
488 catch (InterruptedException e) {
489 }
490 if (replyObj instanceof FtdiException) {
491 throw (FtdiException)replyObj;
492 }
493 byte[] reply = (byte[])replyObj;
494 if (Convert.bytesToShort(reply, FtdiServer.POSN_FUNCTION)
495 == FtdiServer.FUNC_EXCEPTION) {
496 String text = new String(reply, FtdiServer.POSN_EXCPTEXT,
497 reply.length - FtdiServer.POSN_EXCPTEXT);
498 throw new FtdiException(text);
499 }
500
501 return reply;
502 }
503
504
505 /**
506 ***************************************************************************
507 **
508 ** Closes the network connection silently.
509 **
510 ** @return Whether the connection was open
511 **
512 ***************************************************************************
513 */
514 private boolean closeNetSilent()
515 {
516 boolean wasOpen = true;
517
518 try {
519 wasOpen = closeNet();
520 }
521 catch (FtdiException e) {
522 }
523
524 return wasOpen;
525 }
526
527
528 /**
529 ***************************************************************************
530 **
531 ** Closes the network connection.
532 **
533 ** @return Whether the connection was open
534 **
535 ** @throws FtdiException
536 **
537 ***************************************************************************
538 */
539 private boolean closeNet() throws FtdiException
540 {
541 boolean wasOpen = false;
542 Exception ei = null;
543
544 try {
545 if (srvSock != null) {
546 wasOpen = true;
547 srvSock.close();
548 }
549 }
550 catch (IOException e) {
551 ei = e;
552 }
553 srvSock = null;
554
555 if (ei != null) {
556 throw new FtdiException(ei);
557 }
558
559 return wasOpen;
560 }
561
562 }