001package jmri.jmrix.bachrus;
002
003import java.io.DataInputStream;
004import java.io.OutputStream;
005import java.util.Vector;
006import org.slf4j.Logger;
007import org.slf4j.LoggerFactory;
008import purejavacomm.SerialPortEvent;
009import purejavacomm.SerialPortEventListener;
010
011/**
012 * Converts Stream-based I/O to/from Speedo messages. The "SpeedoInterface" side
013 * sends/receives message objects. The connection to a SpeedoPortController is
014 * via a pair of *Streams, which then carry sequences of characters for
015 * transmission. Note that this processing is handled in an independent thread.
016 * <p>
017 * Removed Runnable implementation and methods for it.
018 *
019 * @author Bob Jacobsen Copyright (C) 2001
020 * @author Andrew Crosland Copyright (C) 2010
021 * @author Andrew Berridge Copyright (C) 2010 for gnu io (RXTX)
022 */
023public class SpeedoTrafficController implements SpeedoInterface, SerialPortEventListener {
024
025    private SpeedoReply reply = new SpeedoReply();
026
027    /**
028     * Create a new SpeedoTrafficController instance.
029     *
030     * @param adaptermemo the associated SystemConnectionMemo
031     */
032    public SpeedoTrafficController(SpeedoSystemConnectionMemo adaptermemo) {
033    }
034
035    // The methods to implement the SpeedoInterface
036
037    protected Vector<SpeedoListener> cmdListeners = new Vector<SpeedoListener>();
038
039    @Override
040    public boolean status() {
041        return (ostream != null && istream != null);
042    }
043
044    @Override
045    public synchronized void addSpeedoListener(SpeedoListener l) {
046        // add only if not already registered
047        if (l == null) {
048            throw new java.lang.NullPointerException();
049        }
050        if (!cmdListeners.contains(l)) {
051            cmdListeners.addElement(l);
052        }
053    }
054
055    @Override
056    public synchronized void removeSpeedoListener(SpeedoListener l) {
057        if (cmdListeners.contains(l)) {
058            cmdListeners.removeElement(l);
059        }
060    }
061
062    SpeedoListener lastSender = null;
063
064    @SuppressWarnings("unchecked")
065    protected void notifyReply(SpeedoReply r) {
066        // make a copy of the listener vector to synchronized (not needed for transmit?)
067        Vector<SpeedoListener> v;
068        synchronized (this) {
069            v = (Vector<SpeedoListener>) cmdListeners.clone();
070        }
071        // forward to all listeners
072        int cnt = v.size();
073        for (int i = 0; i < cnt; i++) {
074            SpeedoListener client = v.elementAt(i);
075            try {
076                // skip forwarding to the last sender for now, we'll get them later
077                if (lastSender != client) {
078                    client.reply(r);
079                }
080            } catch (Exception e) {
081                log.warn("notify: During dispatch to {} Exception", client, e);
082            }
083        }
084
085        // Forward to the last listener who send a message.
086        // This is done _second_ so monitoring can have already stored the reply
087        // before a response is sent.
088        if (lastSender != null) {
089            lastSender.reply(r);
090        }
091    }
092
093    // methods to connect/disconnect to a source of data in a LnPortController
094
095    private SpeedoPortController controller = null;
096
097    /**
098     * Make connection to existing PortController object.
099     * @param p speedo port controller.
100     */
101    public void connectPort(SpeedoPortController p) {
102        istream = p.getInputStream();
103        ostream = p.getOutputStream();
104        if (controller != null) {
105            log.warn("connectPort: connect called while connected");
106        }
107        controller = p;
108    }
109
110    /**
111     * Break connection to existing SpeedoPortController object.
112     * Once broken, attempts to send via "message" member will fail.
113     * @param p speedo port controller.
114     */
115    public void disconnectPort(SpeedoPortController p) {
116        istream = null;
117        ostream = null;
118        if (controller != p) {
119            log.warn("disconnectPort: disconnect called from non-connected LnPortController");
120        }
121        controller = null;
122    }
123
124    // data members to hold the streams
125    DataInputStream istream = null;
126    OutputStream ostream = null;
127
128    /*
129     * Speedo replies end with ";"
130     */
131    boolean endReply(SpeedoReply msg) {
132        // Detect that the reply buffer ends with ";"
133        int num = msg.getNumDataElements();
134        // ptr is offset of last element in SpeedoReply
135        int ptr = num - 1;
136        if (msg.getElement(ptr) != ';') {
137            return false;
138        }
139        unsolicited = true;
140        return true;
141    }
142
143    private boolean unsolicited;
144
145    /**
146     * Respond to an event triggered by RXTX. In this case we are
147     * only dealing with DATA_AVAILABLE but the other events are left here for
148     * reference.
149     *
150     * @author Andrew Berridge Jan 2010
151     */
152    @Override
153    public void serialEvent(SerialPortEvent event) {
154        switch (event.getEventType()) {
155            case SerialPortEvent.BI:
156            case SerialPortEvent.OE:
157            case SerialPortEvent.FE:
158            case SerialPortEvent.PE:
159            case SerialPortEvent.CD:
160            case SerialPortEvent.CTS:
161            case SerialPortEvent.DSR:
162            case SerialPortEvent.RI:
163            case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
164                break;
165            case SerialPortEvent.DATA_AVAILABLE:
166                // we get here if data has been received
167                //fill the current reply with any data received
168                int replyCurrentSize = this.reply.getNumDataElements();
169                int i;
170                for (i = replyCurrentSize; i < SpeedoReply.maxSize - replyCurrentSize; i++) {
171                    try {
172                        if (istream.available() == 0) {
173                            break; //nothing waiting to be read
174                        }
175                        byte char1 = istream.readByte();
176                        this.reply.setElement(i, char1);
177
178                    } catch (Exception e) {
179                        log.debug("Exception handling reply cause {}", e.getCause(), e);
180                    }
181                    if (endReply(this.reply)) {
182                        sendreply();
183                        break;
184                    }
185                }
186
187                break;
188            default:
189                log.warn("Unhandled event type: {}", event.getEventType());
190                break;
191        }
192    }
193
194    /**
195     * Send the current reply - built using data from serialEvent.
196     */
197    private void sendreply() {
198        //send the reply
199        {
200            final SpeedoReply thisReply = this.reply;
201            if (unsolicited) {
202                thisReply.setUnsolicited();
203            }
204            final SpeedoTrafficController thisTc = this;
205            // return a notification via the queue to ensure end
206            Runnable r = new Runnable() {
207
208                SpeedoReply msgForLater = thisReply;
209                SpeedoTrafficController myTc = thisTc;
210
211                @Override
212                public void run() {
213                    myTc.notifyReply(msgForLater);
214                }
215            };
216            javax.swing.SwingUtilities.invokeLater(r);
217        }
218        //Create a new reply, ready to be filled
219        this.reply = new SpeedoReply();
220    }
221
222    private final static Logger log = LoggerFactory.getLogger(SpeedoTrafficController.class);
223
224}