001package jmri.jmrix.loconet.streamport;
002
003import java.util.NoSuchElementException;
004import jmri.jmrix.loconet.LnPacketizer;
005import jmri.jmrix.loconet.LocoNetSystemConnectionMemo;
006import org.slf4j.Logger;
007import org.slf4j.LoggerFactory;
008
009/**
010 * Converts Stream-based I/O to/from LocoNet messages. The "LocoNetInterface"
011 * side sends/receives LocoNetMessage objects. The connection to a
012 * LnPortController is via a pair of *Streams, which then carry sequences of
013 * characters for transmission.
014 * <p>
015 * Messages come to this via the main GUI thread, and are forwarded back to
016 * listeners in that same thread. Reception and transmission are handled in
017 * dedicated threads by RcvHandler and XmtHandler objects. Those are internal
018 * classes defined here. The thread priorities are:
019 * <ul>
020 *   <li> RcvHandler - at highest available priority
021 *   <li> XmtHandler - down one, which is assumed to be above the GUI
022 *   <li> (everything else)
023 * </ul>
024 * Some of the message formats used in this class are Copyright Digitrax, Inc.
025 * and used with permission as part of the JMRI project. That permission does
026 * not extend to uses in other software products. If you wish to use this code,
027 * algorithm or these message formats outside of JMRI, please contact Digitrax
028 * Inc for separate permission.
029 *
030 * @author Bob Jacobsen Copyright (C) 2001
031 */
032public class LnStreamPortPacketizer extends LnPacketizer {
033
034    public LnStreamPortPacketizer(LocoNetSystemConnectionMemo m) {
035        super(m);
036    }
037
038    public LnStreamPortController streamController = null;
039
040    @Override
041    public boolean isXmtBusy() {
042        if (streamController == null) {
043            return false;
044        }
045        return true;
046    }
047
048    /**
049     * Make connection to existing LnPortController object.
050     *
051     * @param p Port controller to connect to. Save this for a later disconnect
052     *          call
053     */
054    public void connectPort(LnStreamPortController p) {
055        istream = p.getInputStream();
056        ostream = p.getOutputStream();
057        if (controller != null) {
058            log.warn("connectPort: connect called while connected");
059        }
060        streamController = p;
061    }
062
063    /**
064     * Break connection to existing LnPortController object. Once broken,
065     * attempts to send via "message" member will fail.
066     *
067     * @param p previously connected port
068     */
069    public void disconnectPort(LnStreamPortController p) {
070        istream = null;
071        ostream = null;
072        if (streamController != p) {
073            log.warn("disconnectPort: disconnect called from non-connected LnStreamPortController");
074        }
075        streamController = null;
076    }
077
078    /**
079     * Captive class to handle transmission
080     */
081    class XmtHandler implements Runnable {
082
083        @Override
084        public void run() {
085
086            while (!threadStopRequest) {   // loop until asked to stop
087                // any input?
088                try {
089                    // get content; failure is a NoSuchElementException
090                    log.trace("check for input"); // NOI18N
091                    byte msg[] = null;
092                    synchronized (this) {
093                        msg = xmtList.removeFirst();
094                    }
095
096                    // input - now send
097                    try {
098                        if (ostream != null) {
099                            if (!streamController.okToSend()) {
100                                log.debug("LocoNet port not ready to receive"); // NOI18N
101                            }
102                            if (log.isDebugEnabled()) { // avoid String building if not needed
103                                log.debug("start write to stream: {}", jmri.util.StringUtil.hexStringFromBytes(msg)); // NOI18N
104                            }
105                            ostream.write(msg);
106                            ostream.flush();
107                            if (log.isTraceEnabled()) { // avoid String building if not needed
108                                log.trace("end write to stream: {}", jmri.util.StringUtil.hexStringFromBytes(msg)); // NOI18N
109                            }
110                            messageTransmitted(msg);
111                        } else {
112                            // no stream connected
113                            log.warn("sendLocoNetMessage: no connection established"); // NOI18N
114                        }
115                    } catch (java.io.IOException e) {
116                        log.warn("sendLocoNetMessage: IOException: {}", e.toString()); // NOI18N
117                    }
118                } catch (NoSuchElementException e) {
119                    // message queue was empty, wait for input
120                    log.trace("start wait"); // NOI18N
121
122                    new jmri.util.WaitHandler(this);  // handle synchronization, spurious wake, interruption
123
124                    log.trace("end wait"); // NOI18N
125                }
126            }
127        }
128    }
129
130    /**
131     * Invoked at startup to start the threads needed here.
132     */
133    @Override
134    public void startThreads() {
135        int priority = Thread.currentThread().getPriority();
136        log.debug("startThreads current priority = {} max available = " + Thread.MAX_PRIORITY + " default = " + Thread.NORM_PRIORITY + " min available = " + Thread.MIN_PRIORITY, priority); // NOI18N
137
138        // make sure that the xmt priority is no lower than the current priority
139        int xmtpriority = (Thread.MAX_PRIORITY - 1 > priority ? Thread.MAX_PRIORITY - 1 : Thread.MAX_PRIORITY);
140        // start the XmtHandler in a thread of its own
141        if (xmtHandler == null) {
142            xmtHandler = new XmtHandler();
143        }
144        xmtThread = new Thread(xmtHandler, "LocoNet transmit handler"); // NOI18N
145        log.debug("Xmt thread starts at priority {}", xmtpriority); // NOI18N
146        xmtThread.setDaemon(true);
147        xmtThread.setPriority(Thread.MAX_PRIORITY - 1);
148        xmtThread.start();
149
150        // start the RcvHandler in a thread of its own
151        if (rcvHandler == null) {
152            rcvHandler = new RcvHandler(this);
153        }
154        rcvThread = new Thread(rcvHandler, "LocoNet receive handler"); // NOI18N
155        rcvThread.setDaemon(true);
156        rcvThread.setPriority(Thread.MAX_PRIORITY);
157        rcvThread.start();
158    }
159
160    private final static Logger log = LoggerFactory.getLogger(LnStreamPortPacketizer.class);
161
162}