All Downloads are FREE. Search and download functionalities are using the official Maven repository.

me.legrange.swap.SerialModem Maven / Gradle / Ivy

There is a newer version: 2.2
Show newest version
package me.legrange.swap;

import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * An interface providing access through the serial port to the SWAP modem.
 *
 * @since 1.0
 * @author Gideon le Grange https://github.com/GideonLeGrange *
 */
public final class SerialModem implements SwapModem {

    public SerialModem(String port, int baud) {
        this.port = port;
        this.baud = baud;
    }

    @Override
    public void open() throws SwapException {
        com = ComPort.open(port, baud);
        running = true;
        reader = new Reader();
        reader.setDaemon(true);
        reader.setName(String.format("%s Reader Thread", getClass().getSimpleName()));
        reader.start();
        if (setup != null) {
            setSetup(setup);
        }
    }

    @Override
    public void close() throws SerialException {
        running = false;
        com.close();
    }

    @Override
    public boolean isOpen() {
        return running;
    }

    @Override
    public synchronized void send(SwapMessage msg) throws SerialException {
        com.send(msg.getText() + "\r");
        fireEvent(msg, ReceiveTask.Direction.OUT);
    }

    @Override
    public void addListener(MessageListener l) {
        synchronized (listeners) {
            listeners.add(l);
        }
    }

    @Override
    public void removeListener(MessageListener l) {
        synchronized (listeners) {
            listeners.remove(l);
        }
    }

    @Override
    public ModemSetup getSetup() throws SerialException {
        if (setup == null) {
            if (running) {
                synchronized (this) {
                    enterCommandMode();
                    setup = new ModemSetup(readATasInt("ATCH?"), readATasHex("ATSW?"),
                            readATasHex("ATDA?"));
                    leaveCommandMode();
                }
            } else {
                setup = new ModemSetup(0, 0, 0);
            }
        }
        return setup;
    }

    @Override
    public void setSetup(ModemSetup setup) throws SerialException {
        synchronized (this) {
            if (running) {
                enterCommandMode();
                sendATCommand(String.format("ATCH=%2d", setup.getChannel()));
                sendATCommand(String.format("ATSW=%4h", setup.getNetworkID()));
                sendATCommand(String.format("ATDA=%2d", setup.getDeviceAddress()));
                leaveCommandMode();
            }
            this.setup = setup;
        }
    }

    @Override
    public Type getType() {
        return Type.SERIAL;
    }

    /**
     * Get the name of the serial port used by this modem.
     *
     * @return The name of the serial port
     */
    public String getPort() {
        return port;
    }

    /**
     * Get the serial speed this modem connects at
     *
     * @return The serial speed
     */
    public int getBaud() {
        return baud;
    }

    private int readATasInt(String cmd) throws SerialException {
        String res = readAT(cmd);
        try {
            return Integer.parseInt(res);
        } catch (NumberFormatException e) {
            throw new SerialException(String.format("Malformed integer response '%s' (%s) to %s comamnd", res, asHex(res), cmd), e);
        }
    }

    private int readATasHex(String cmd) throws SerialException {
        String res = readAT(cmd);
        try {
            return Integer.parseInt(res, 16);
        } catch (NumberFormatException e) {
            throw new SerialException(String.format("Malformed hex response '%s' (%s) to %s comamnd", res, asHex(res), cmd), e);
        }
    }

    private String readAT(String cmd) throws SerialException {
        String res = sendATCommand(cmd);
        switch (res) {
            case "ERROR":
                throw new SerialException(String.format("Error on %s command", cmd));
            case "OK":
                throw new SerialException(String.format("Unexpected OK in %s command", cmd));
            default:
                return res;
        }
    }

    private void enterCommandMode() throws SerialException {
        if (mode == Mode.COMMAND) {
            return;
        }
        while (mode == Mode.INIT) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException ex) {
                Logger.getLogger(SerialModem.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        int count = 3;
        while (count > 0) {
            if (tryCommandMode()) {
                return;
            }
            count--;
        }
        throw new SerialException("Timed out waiting for command mode");
    }

    private boolean tryCommandMode() throws SerialException {
        int count = 0;
        com.send("+++");
        while ((mode != Mode.COMMAND) && (count < 15)) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException ex) {
                if (!running) {
                    throw new SerialException("Modem stopped while entering command mode");
                }

            }
            count++;
        }
        return (mode == Mode.COMMAND);
    }

    private void leaveCommandMode() throws SerialException {
        if (mode == Mode.DATA) {
            return;
        }
        com.send("ATO\r");
        while (mode != Mode.DATA) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException ex) {
            }

        }
    }

    private String sendATCommand(String cmd) throws SerialException {
        com.send(cmd + "\r");
        String res;
        try {
            return results.take();
        } catch (InterruptedException ex) {
            throw new SerialException("Interruped waiting for AT response");
        }
    }

    /**
     * send the received message to listeners
     */
    private void fireEvent(SwapMessage msg, ReceiveTask.Direction dir) {
        synchronized (listeners) {
            for (MessageListener l : listeners) {
                pool.submit(new ReceiveTask(l, msg, dir));
            }
        }
    }

    private enum Mode {

        INIT, DATA, COMMAND
    };

    private ComPort com;
    private Mode mode = Mode.DATA;
    private ModemSetup setup;
    private final BlockingQueue results = new LinkedBlockingQueue<>();
    private Reader reader;
    private boolean running;
    private final List listeners = new CopyOnWriteArrayList<>();
    private final int baud;
    private final String port;
    private final ExecutorService pool = Executors.newCachedThreadPool(new ThreadFactory() {

        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r, "SWAP Listener Notification");
            t.setDaemon(true);
            return t;
        }
    });

    /**
     * The reader thread that receives data from the modem, unpacks it into
     * messages and send the messages to the listeners.
     */
    private class Reader extends Thread {

        @Override
        public void run() {
            while (running) {
                try {
                    String in = com.read();
                    if (in.length() == 0) {
                        continue;
                    }
                    if (in.charAt(0) == '(') {
                        if (in.length() >= 12) {
                            mode = Mode.DATA;
                            fireEvent(new SerialMessage(in), ReceiveTask.Direction.IN);
                        } else {
                            // truncated SWAP message, drop silently
                        }
                    } else {
                        // handle AT responses here
                        switch (in) {
                            case "OK-Command mode":
                                mode = Mode.COMMAND;
                                break;
                            case "Modem ready!":
                            case "OK-Data mode":
                                mode = Mode.DATA;
                                break;
                            case "OK":
                            case "ERROR":
                            default:
                                results.add(in);

                        }
                    }
                } catch (SerialException ex) {
                    Logger.getLogger(SerialModem.class.getName()).log(Level.SEVERE, null, ex);
                } catch (DecodingException ex) {
                    Logger.getLogger(SerialModem.class.getName()).log(Level.SEVERE, null, ex);
                } catch (Throwable ex) {
                    Logger.getLogger(SerialModem.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    }

    private String asHex(String text) {
        byte[] bytes = text.getBytes();
        StringBuilder buf = new StringBuilder();
        for (byte b : bytes) {
            buf.append(String.format("%2x", b));
        }
        return buf.toString();
    }

    private static class ReceiveTask implements Runnable {

        private enum Direction {

            IN, OUT;
        }

        private ReceiveTask(MessageListener listener, SwapMessage msg, Direction dir) {
            this.msg = msg;
            this.l = listener;
            this.dir = dir;
        }

        @Override
        public void run() {
            try {
                if (dir == Direction.IN) {
                    l.messageReceived(msg);
                } else {
                    l.messageSent(msg);
                }
            } catch (Throwable e) {
                Logger.getLogger(SerialModem.class.getName()).log(Level.SEVERE, null, e);
            }
        }

        private final SwapMessage msg;
        private final MessageListener l;
        private final Direction dir;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy