org.smslib.CSerialDriver.java Source code

Java tutorial

Introduction

Here is the source code for org.smslib.CSerialDriver.java

Source

// SMSLib for Java
// An open-source API Library for sending and receiving SMS via a GSM modem.
// Copyright (C) 2002-2007, Thanasis Delenikas, Athens/GREECE
// Web Site: http://www.smslib.org
//
// SMSLib is distributed under the LGPL license.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

package org.smslib;

import serial.*;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.TooManyListenersException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;
import org.smslib.CNewMsgMonitor.State;
import org.smslib.logging.LoggingInputStream;
import org.smslib.logging.LoggingOutputStream;

import static org.apache.commons.lang3.StringEscapeUtils.escapeJava;

public class CSerialDriver implements SerialPortEventListener {
    /** Prints to console a selection of full lines read and written to the serial port. */
    private static final boolean STREAM_LOGGING_ENABLED;
    static {
        boolean streamLoggingEnabled = false;
        try {
            String val = System.getProperty("smslib.streamlogging");
            streamLoggingEnabled = Boolean.parseBoolean(val);
        } catch (Throwable t) {
            t.printStackTrace(System.err);
        }
        STREAM_LOGGING_ENABLED = streamLoggingEnabled;
    }
    private static final int DELAY = 500;
    private static final int DELAY_AFTER_WRITE = 100;
    private static final int RECV_TIMEOUT = 30 * 1000;
    private static final int BUFFER_SIZE = 16384;

    /** The name of the serial port this conencts to. */
    private String port;
    private int baud;
    private CommPortIdentifier commPortIdentifier;
    /** The serial port this connects to. */
    public SerialPort serialPort;
    /** Input stream of the serial port this connects to. */
    private InputStream inStream;
    /** Output stream of the serial port this connects to. */
    private OutputStream outStream;
    private CNewMsgMonitor newMsgMonitor;
    /** Set <code>true</code> to stop current operations. */
    private volatile boolean stopFlag;
    /** The logger for this driver. */
    private Logger log;
    private CService srv;
    private String lastClearedBuffer = "";

    public CSerialDriver(String port, int baud, CService srv) {
        this.port = port;
        this.baud = baud;
        this.srv = srv;
        this.log = Logger.getLogger(CSerialDriver.class);
    }

    public void setPort(String port) {
        this.port = port;
    }

    public String getPort() {
        return port;
    }

    public int getBaud() {
        return baud;
    }

    public String getLastClearedBuffer() {
        return lastClearedBuffer;
    }

    public void setNewMsgMonitor(CNewMsgMonitor monitor) {
        this.newMsgMonitor = monitor;
    }

    public void killMe() {
        stopFlag = true;
    }

    public void open() throws IOException, NoSuchPortException, PortInUseException,
            UnsupportedCommOperationException, TooManyListenersException {
        if (log != null)
            log.info("Connecting to serial port: " + port + " @ " + baud);

        commPortIdentifier = CommPortIdentifier.getPortIdentifier(getPort());
        serialPort = (SerialPort) commPortIdentifier.open("FrontlineSMS", 1971);
        serialPort.addEventListener(this);
        serialPort.notifyOnDataAvailable(true);
        /*
         * There is no handling for these notifications except for logging, so I have
         * removed setting them.  Also, some combination of these notifications cause
         * my machine to lock up if certain devices are attached.
           serialPort.notifyOnOutputEmpty(true);
           serialPort.notifyOnBreakInterrupt(true);
           serialPort.notifyOnFramingError(true);
           serialPort.notifyOnOverrunError(true);
           serialPort.notifyOnParityError(true);
         */
        serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_RTSCTS_IN);
        serialPort.setSerialPortParams(getBaud(), SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
                SerialPort.PARITY_NONE);
        serialPort.setInputBufferSize(BUFFER_SIZE);
        serialPort.setOutputBufferSize(BUFFER_SIZE);
        serialPort.enableReceiveTimeout(RECV_TIMEOUT);

        if (STREAM_LOGGING_ENABLED) {
            String modemName = port.replace('/', '_');
            PrintWriter fileLog;
            try {
                File f = new File(System.getProperty("user.home") + "/.frontlinesms/serial-logs",
                        modemName + "_" + System.currentTimeMillis() + ".log");
                System.out.println("Logging serial streams for " + port + " to " + f.getAbsolutePath());
                f.getParentFile().mkdirs();
                fileLog = new PrintWriter(f, "UTF-8");
            } catch (Exception ex) {
                ex.printStackTrace(System.err);
                throw new RuntimeException(ex);
            }
            inStream = new LoggingInputStream(serialPort.getInputStream(), fileLog, ">>${thread}>>TX>>${stack}>>",
                    '\r', '\n');
            outStream = new LoggingOutputStream(serialPort.getOutputStream(), fileLog,
                    ">>${thread}>>RX>>${stack}>>", '\r', '\n');
        } else {
            inStream = serialPort.getInputStream();
            outStream = serialPort.getOutputStream();
        }

        //bjdw added to try and catch "WaitCommEvent: Error 5" when usb port is disconnected
        serialPort.notifyOnCTS(true);
    }

    public void close() {
        if (log != null)
            log.info("Disconnecting from serial port: " + port);
        // TODO is this check necessary?  Possibly not...
        if (serialPort != null) {
            serialPort.removeEventListener();
            serialPort.close();
        }
    }

    public void serialEvent(SerialPortEvent event) {
        int eventType = event.getEventType();
        if (eventType == SerialPortEvent.BI) {
            return;
        }
        if (eventType == SerialPortEvent.OE) {
            if (log != null)
                log.error("COMM-ERROR: Overrun Error!");
            return;
        }
        if (eventType == SerialPortEvent.FE) {
            if (log != null)
                log.error("COMM-ERROR: Framing Error!");
            return;
        }
        if (eventType == SerialPortEvent.PE) {
            if (log != null)
                log.error("COMM-ERROR: Parity Error!");
            return;
        }
        if (eventType == SerialPortEvent.CD) {
            return;
        }
        if (eventType == SerialPortEvent.CTS) {
            //numberOfCTSevents++;
            if (/*(numberOfCTSevents>=MAX_CTS_EVENTS_BEFORE_CLOSE) &&*/ !event.getNewValue()) {
                //try disconnect
                close();
            }
            return;
        }
        if (eventType == SerialPortEvent.DSR) {
            return;
        }
        if (eventType == SerialPortEvent.RI) {
            return;
        }
        if (eventType == SerialPortEvent.OUTPUT_BUFFER_EMPTY) {
            return;
        }
        if (eventType == SerialPortEvent.DATA_AVAILABLE) {
            if (newMsgMonitor != null)
                newMsgMonitor.raise(State.DATA);
            return;
        }
    }

    public void clearBufferCheckCMTI() throws IOException {
        if (log != null)
            log.debug("SerialDriver(): clearBufferCheckCMTI() called");
        String bufferContent = readBuffer();
        lastClearedBuffer = bufferContent;
        if (log != null)
            log.debug("ME(CL): " + escapeJava(bufferContent));
        if (newMsgMonitor != null && newMsgMonitor.getState() != State.CMTI) {
            final String txt = bufferContent;
            newMsgMonitor
                    .raise((txt.indexOf("+CMTI:") >= 0 || txt.indexOf("+CDSI:") >= 0) ? State.CMTI : State.IDLE);
        }
    }

    public String readBuffer() throws IOException {
        StringBuilder buffer = new StringBuilder(BUFFER_SIZE);

        while (dataAvailable()) {
            int c = inStream.read();
            if (c == -1)
                break;
            buffer.append((char) c);
        }

        return buffer.toString();
    }

    public void emptyBuffer() throws IOException {
        if (log != null)
            log.debug("SerialDriver(): emptyBuffer() called");
        CUtils.sleep_ignoreInterrupts(DELAY);
        while (dataAvailable())
            inStream.read();
    }

    public void clearBuffer() throws IOException {
        CUtils.sleep_ignoreInterrupts(DELAY);
        clearBufferCheckCMTI();
    }

    public void send(String s) throws IOException {
        if (log != null)
            log.debug("TE: " + escapeJava(s));

        for (int i = 0; i < s.length(); i++) {
            outStream.write((byte) s.charAt(i));
        }
        outStream.flush();
        CUtils.sleep_ignoreInterrupts(DELAY_AFTER_WRITE);
    }

    public void send(char c) throws IOException {
        outStream.write((byte) c);
        outStream.flush();
        CUtils.sleep_ignoreInterrupts(DELAY_AFTER_WRITE);
    }

    public void send(byte c) throws IOException {
        outStream.write(c);
        outStream.flush();
        CUtils.sleep_ignoreInterrupts(DELAY_AFTER_WRITE);
    }

    public void skipBytes(int numOfBytes) throws IOException {
        int c, count = 0;
        while (count < numOfBytes) {
            c = inStream.read();
            // Looks dodgy - if c IS -1, then we've reached the end of the stream, and should get out of here.
            if (c != -1)
                count++;
        }
    }

    public boolean dataAvailable() throws IOException {
        int available = inStream.available();
        return !stopFlag && available > 0;
    }

    public String getResponse() throws IOException {
        final int MAX_RETRIES = 3;
        int retry = 0;
        final StringBuilder buffer = new StringBuilder(BUFFER_SIZE);

        while (retry < MAX_RETRIES) {
            try {
                readResponseToBuffer(buffer);
                retry = MAX_RETRIES;
            } catch (ServiceDisconnectedException e) {
                return "+ERROR:\r\n";
            } catch (IOException e) {
                if (++retry <= MAX_RETRIES) {
                    if (log != null)
                        log.info("IOException reading from serial port.  Will retry.", e);
                    try {
                        Thread.sleep(DELAY);
                    } catch (InterruptedException ex) {
                    }
                } else
                    throw e;
            }
        }
        if (log != null)
            log.debug("ME: " + escapeJava(buffer.toString()));
        clearBufferCheckCMTI();

        // check to see if any phone call alerts have been triggered
        if (buffer.indexOf("RING") > 0) { // FIXME should this actually read ">= 0"?!
            if (srv.isConnected()) {
                if (srv.getCallHandler() != null) {
                    Pattern p = Pattern.compile("\\+?\\d+");
                    Matcher m = p.matcher(buffer);
                    String phone = m.find() ? m.group() : "";
                    srv.getCallHandler().received(srv, new CIncomingCall(phone, new java.util.Date()));
                }

                // strip all content relating to RING, and return the rest of the response
                return buffer.toString()
                        .replaceAll("\\s*RING\\s+[\\p{ASCII}]CLIP[[\\p{Alnum}][\\p{Punct}] ]+\\s\\s", "");
            } else
                return buffer.toString();
        } else {
            return buffer.toString();
        }
    }

    void readToBuffer(StringBuilder buffer) throws IOException, ServiceDisconnectedException {
        while (true) {
            if (stopFlag)
                throw new ServiceDisconnectedException();
            int c = inStream.read();
            if (c == -1) {
                buffer.delete(0, buffer.length());
                break;
            }
            buffer.append((char) c);

            if ((c == '\r') || (c == '\n'))
                return;
        }
    }

    /** this is the new version of the method which we want to adopt once the old version is properly unit tested. */
    void readResponseToBuffer(StringBuilder buffer) throws IOException, ServiceDisconnectedException {
        while (true) {
            readToBuffer(buffer);
            String response = buffer.toString();

            if (response.length() == 0 || response.matches("\\s*\\p{ASCII}*\\s+OK\\s")
                    || response.matches("\\s*\\p{ASCII}*\\s+READY\\s+")
                    || response.matches("\\s*\\p{ASCII}*\\s+ERROR(:( \\w+)+)?\\s")
                    || response.matches("\\s*\\p{ASCII}*\\s+SIM PIN\\s"))
                return;
            else if (response.matches("\\s*[+]((CMTI)|(CDSI))[:][^\r\n]*[\r\n]")) {
                if (log != null)
                    log.debug("ME: " + escapeJava(buffer.toString()));
                buffer.delete(0, buffer.length());
                if (newMsgMonitor != null)
                    newMsgMonitor.raise(CNewMsgMonitor.State.CMTI);
            }
        }
    }

    public void ownershipChange(int type) {
        log.info("CSerialDriver.ownershipChange() : " + type);
    }
}

@SuppressWarnings("serial")
class ServiceDisconnectedException extends Exception {
}