/** * */ package net.frontlinesms.messaging; import java.io.*; import java.util.*; import net.frontlinesms.CommUtils; import serial.*; /** * A commandline utility for detecting connected AT devices. * The detection workflow was taken from ComTest in SMSLib. * @author Alex Anderson alex@frontlinesms.com */ public class AllModemsDetector { public static void main(String[] args) { AllModemsDetector amd = new AllModemsDetector(); ATDeviceDetector[] detectors = amd.detectBlocking(); printReport(detectors); } private Logger log = new Logger(getClass()); private ATDeviceDetector[] detectors; /** Trigger detection, and return the results when it is completed. */ public ATDeviceDetector[] detectBlocking() { detect(); waitUntilDetectionComplete(detectors); return getDetectors(); } /** Trigger detection. */ public void detect() { log.trace("Starting device detection..."); Set<ATDeviceDetector> detectors = new HashSet<ATDeviceDetector>(); Enumeration<CommPortIdentifier> ports = CommUtils.getPortIdentifiers(); while(ports.hasMoreElements()) { CommPortIdentifier port = ports.nextElement(); if(port.getPortType() == CommPortIdentifier.PORT_SERIAL) { ATDeviceDetector d = new ATDeviceDetector(port); detectors.add(d); d.start(); } else { log.info("Ignoring non-serial port: " + port.getName()); } } this.detectors = detectors.toArray(new ATDeviceDetector[0]); log.trace("All detectors started."); } /** Get the detectors. */ public ATDeviceDetector[] getDetectors() { return detectors; } /** Blocks until all detectors have completed execution. */ private static void waitUntilDetectionComplete(ATDeviceDetector[] detectors) { boolean completed; do { completed = true; for (ATDeviceDetector portDetector : detectors) { if(!portDetector.isFinished()) { completed = false; } } Utils.sleep(500); } while(!completed); } /** Prints a report to {@link System#out} detailing the devices that were detected. */ private static void printReport(ATDeviceDetector[] completedDetectors) { // All detectors are finished, so print a report for(ATDeviceDetector d : completedDetectors) { System.out.println("---"); System.out.println("PORT : " + d.getPortIdentifier().getName()); if(d.isDetected()) { System.out.println("SERIAL : " + d.getSerial()); System.out.println("BAUD : " + d.getMaxBaudRate()); } else { System.out.println("DETECTION FAILED"); System.out.println("> " + d.getExceptionMessage()); } } } } class ATDeviceDetector extends Thread { /** Valid baud rates */ private static final int[] BAUD_RATES = { 9600, 14400, 19200, 28800, 33600, 38400, 56000, 57600, 115200, 230400, 460800, 921600 }; /** Logger */ private final Logger log = new Logger(this.getClass()); /** Port this is detecting on */ private final CommPortIdentifier portIdentifier; /** The top speed the device was detected at. */ private int maxBaudRate; /** The serial number of the detected device. */ private String serial; /** <code>true</code> when the detection thread has finished. */ private boolean finished; private String exceptionMessage; public ATDeviceDetector(CommPortIdentifier port) { super("ATDeviceDetector: " + port.getName()); this.portIdentifier = port; } public void run() { for(int baud : BAUD_RATES) { SerialPort serialPort = null; InputStream in = null; OutputStream out = null; try { serialPort = portIdentifier.open("ATDeviceDetector", 2000); serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_RTSCTS_IN); serialPort.setSerialPortParams(baud, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); in = serialPort.getInputStream(); out = serialPort.getOutputStream(); serialPort.enableReceiveTimeout(1000); log.trace("LOOPING."); // discard all data currently waiting on the input stream Utils.readAll(in); Utils.writeCommand(out, "AT"); Utils.sleep(1000); String response = Utils.readAll(in); if(!Utils.isResponseOk(response)) { throw new ATDeviceDetectionException("Bad response: " + response); } else { Utils.writeCommand(out, "AT+CGSN"); response = Utils.readAll(in); if(!Utils.isResponseOk(response)) { throw new ATDeviceDetectionException("Bad response to request for serial number: " + response); } else { String serial = Utils.trimResponse("AT+CGSN", response); log.debug("Found serial: " + serial); if(this.serial != null) { // There was already a serial detected. Check if it's the same as // what we've just got. if(!this.serial.equals(serial)) { log.info("New serial detected: '" + serial + "'. Replacing previous: '" + this.serial + "'"); } } this.serial = serial; maxBaudRate = Math.max(maxBaudRate, baud); } } } catch(Exception ex) { log.info("Problem connecting to device.", ex); this.exceptionMessage = ex.getMessage(); } finally { // Close any open streams if(out != null) try { out.close(); } catch(Exception ex) { log.warn("Error closing output stream.", ex); } if(in != null) try { in.close(); } catch(Exception ex) { log.warn("Error closing input stream.", ex); } if(serialPort != null) try { serialPort.close(); } catch(Exception ex) { log.warn("Error closing serial port.", ex); } } } finished = true; log.info("Detection completed on port: " + this.portIdentifier.getName()); } //> ACCESSORS public boolean isFinished() { return finished; } public boolean isDetected() { return this.maxBaudRate > 0; } public CommPortIdentifier getPortIdentifier() { return portIdentifier; } public int getMaxBaudRate() { return maxBaudRate; } public String getSerial() { assert(isDetected()) : "Cannot get serial if no device was detected."; return serial; } public String getExceptionMessage() { assert(!isDetected()) : "Cannot get Throwable clause if device was detected successfully."; return exceptionMessage; } } class Utils { /** Calls {@link Thread#sleep(long)} and ignores {@link InterruptedException}s thrown. */ public static final void sleep(long millis) { try { Thread.sleep(millis); } catch(InterruptedException ex) { // ignore } } /** * Read all bytes available on the input stream, and concatenates them into a string. * N.B. This casts bytes read directly to characters. */ public static final String readAll(InputStream in) throws IOException { StringBuilder bob = new StringBuilder(); int c; while((c = in.read()) != -1) { bob.append((char) c); } return bob.toString(); } /** Writes the supplied command to the output stream, followed by a \r character */ public static final void writeCommand(OutputStream out, String command) throws IOException { for(char c : command.toCharArray()) out.write(c); out.write('\r'); } /** @return <code>true</code> if the response contains "OK" */ public static final boolean isResponseOk(String response) { return response.indexOf("OK") != -1; } /** @return the response with the original command, "OK" and all trailing and leading whitespace removed */ public static final String trimResponse(String command, String response) { return response.replace("OK", "").replace(command, "").trim(); } } /** Quick stub of a logger to send things to the command line. You'll want to replace this with something proper. */ class Logger { /** Set this to <code>true</code> if you want logging to be printed. */ private static final boolean DO_PRINTING = true; /** Description of this logger */ private final String description; /** Create a new logger for the supplied class */ public Logger(Class<?> clazz) { this.description = clazz.getSimpleName(); } public void trace(String s) { out("TRACE", s); } public void debug(String s) { out("DEBUG", s); } public void info(String s) { out("INFO", s); } public void info(String message, Throwable t) { out("INFO", message, t); } public void warn(String message, Throwable t) { out("WARN", message, t); } private void out(String level, String message) { out(level, message, null); } private void out(String level, String message, Throwable t) { if(!DO_PRINTING) return; PrintStream out = t == null ? System.out : System.err; out.println("[" + Thread.currentThread().getName() + " : " + description + "] " + level + ": " + message); if(t != null) t.printStackTrace(); } } /** Exception thrown when detecting an AT device. */ @SuppressWarnings("serial") class ATDeviceDetectionException extends Exception { public ATDeviceDetectionException(String message) { super(message); } }