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 jmri.jmrix.purejavacomm.SerialPortEvent;
009import jmri.jmrix.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        
135        // Check for the SPC200R, the first byte is always 0x50, 0x51, 0x52 or 0x53
136        int value = msg.getElement(0);
137        if (value == 0x50 || value == 0x51 || value == 0x52 || value == 0x53) {
138            // SPC200R has a 7 byte output
139            if (num != 7) {
140                return false;
141            }
142        }
143        else
144        {
145            // Check for ';' as Bachrus and KPF-Zeller use ; as termination
146            
147            // ptr is offset of last element in SpeedoReply
148            int ptr = num - 1;
149            if (msg.getElement(ptr) != ';') {
150                return false;
151            }
152      }
153
154        unsolicited = true;
155        return true;
156    }
157
158    private boolean unsolicited;
159
160    /**
161     * Respond to an event triggered by RXTX. In this case we are
162     * only dealing with DATA_AVAILABLE but the other events are left here for
163     * reference.
164     */
165    @Override
166    public void serialEvent(SerialPortEvent event) {
167        switch (event.getEventType()) {
168            case SerialPortEvent.BI:
169            case SerialPortEvent.OE:
170            case SerialPortEvent.FE:
171            case SerialPortEvent.PE:
172            case SerialPortEvent.CD:
173            case SerialPortEvent.CTS:
174            case SerialPortEvent.DSR:
175            case SerialPortEvent.RI:
176            case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
177                break;
178            case SerialPortEvent.DATA_AVAILABLE:
179                // we get here if data has been received
180                //fill the current reply with any data received
181                int replyCurrentSize = this.reply.getNumDataElements();
182                //log.debug("SerialPortEvent: DATA_AVAILABEL, number of data elements {}", replyCurrentSize);
183                int i;
184                for (i = replyCurrentSize; i < SpeedoReply.maxSize - replyCurrentSize; i++) {
185                    try {
186                        if (istream.available() == 0) {
187                            break; //nothing waiting to be read
188                        }
189                        byte char1 = istream.readByte();
190                        this.reply.setElement(i, char1);
191
192                    } catch (Exception e) {
193                        log.debug("Exception handling reply cause {}", e.getCause(), e);
194                    }
195                    if (endReply(this.reply)) {
196                        sendreply();
197                        break;
198                    }
199                }
200
201                break;
202            default:
203                log.warn("Unhandled event type: {}", event.getEventType());
204                break;
205        }
206    }
207
208    /**
209     * Send the current reply - built using data from serialEvent.
210     */
211    private void sendreply() {
212        //send the reply
213        {
214            final SpeedoReply thisReply = this.reply;
215            if (unsolicited) {
216                thisReply.setUnsolicited();
217            }
218            final SpeedoTrafficController thisTc = this;
219            // return a notification via the queue to ensure end
220            Runnable r = new Runnable() {
221
222                SpeedoReply msgForLater = thisReply;
223                SpeedoTrafficController myTc = thisTc;
224
225                @Override
226                public void run() {
227                    myTc.notifyReply(msgForLater);
228                }
229            };
230            javax.swing.SwingUtilities.invokeLater(r);
231        }
232        //Create a new reply, ready to be filled
233        this.reply = new SpeedoReply();
234    }
235
236    private final static Logger log = LoggerFactory.getLogger(SpeedoTrafficController.class);
237
238}