001package jmri.jmrix.tmcc;
002
003import java.io.DataInputStream;
004import jmri.jmrix.AbstractMRListener;
005import jmri.jmrix.AbstractMRMessage;
006import jmri.jmrix.AbstractMRReply;
007import jmri.jmrix.AbstractMRTrafficController;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011/**
012 * Convert Stream-based I/O to/from TMCC serial messages.
013 * <p>
014 * The "SerialInterface" side sends/receives message objects.
015 * <p>
016 * The connection to a SerialPortController is via a pair of *Streams, which
017 * then carry sequences of characters for transmission. Note that this
018 * processing is handled in an independent thread.
019 * <p>
020 * This handles the state transitions, based on the necessary state in each
021 * message.
022 * <p>
023 * Handles initialization, polling, output, and input for multiple Serial Nodes.
024 *
025 * @author Bob Jacobsen Copyright (C) 2003, 2006
026 * @author Bob Jacobsen, Dave Duchamp, multiNode extensions, 2004
027 */
028public class SerialTrafficController extends AbstractMRTrafficController implements SerialInterface {
029
030    /**
031     * Create a new TMCC SerialTrafficController instance.
032     *
033     * @param adaptermemo the associated SystemConnectionMemo
034     */
035    public SerialTrafficController(TmccSystemConnectionMemo adaptermemo) {
036        super();
037        mMemo = adaptermemo;
038        // entirely poll driven, so reduce interval
039        mWaitBeforePoll = 25; // default = 25
040        log.debug("creating a new TMCCTrafficController object");
041    }
042
043    // The methods to implement the SerialInterface
044
045    @Override
046    public synchronized void addSerialListener(SerialListener l) {
047        this.addListener(l);
048    }
049
050    @Override
051    public synchronized void removeSerialListener(SerialListener l) {
052        this.removeListener(l);
053    }
054
055    @Override
056    protected AbstractMRMessage enterProgMode() {
057        log.warn("enterProgMode doesn't make sense for TMCC serial");
058        return null;
059    }
060
061    @Override
062    protected AbstractMRMessage enterNormalMode() {
063        return null;
064    }
065
066    /**
067     * Reference to the system connection memo.
068     */
069    TmccSystemConnectionMemo mMemo = null;
070
071    /**
072     * Get access to the system connection memo associated with this traffic
073     * controller.
074     *
075     * @return associated systemConnectionMemo object
076     */
077    public TmccSystemConnectionMemo getSystemConnectionMemo() {
078        return mMemo;
079    }
080
081    /**
082     * Set the system connection memo associated with this traffic controller.
083     *
084     * @param m associated systemConnectionMemo object
085     */
086    public void setSystemConnectionMemo(TmccSystemConnectionMemo m) {
087        mMemo = m;
088    }
089
090    /**
091     * Forward a SerialMessage to all registered SerialInterface listeners.
092     */
093    @Override
094    protected void forwardMessage(AbstractMRListener client, AbstractMRMessage m) {
095        ((SerialListener) client).message((SerialMessage) m);
096    }
097
098    /**
099     * Forward a SerialReply to all registered SerialInterface listeners.
100     */
101    @Override
102    protected void forwardReply(AbstractMRListener client, AbstractMRReply m) {
103        ((SerialListener) client).reply((SerialReply) m);
104    }
105
106    /**
107     * Handle initialization, output and polling for TMCC from within the
108     * running thread.
109     */
110    @Override
111    protected synchronized AbstractMRMessage pollMessage() {
112        // no polling in this protocol
113        return null;
114    }
115
116    @Override
117    protected AbstractMRListener pollReplyHandler() {
118        return null;
119    }
120
121    /**
122     * Forward a preformatted message to the actual interface.
123     */
124    @Override
125    public void sendSerialMessage(SerialMessage m, SerialListener reply) {
126        sendMessage(m, reply);
127    }
128
129    @Override
130    protected AbstractMRReply newReply() {
131        return new SerialReply();
132    }
133
134    @Override
135    protected boolean endOfMessage(AbstractMRReply msg) {
136        // our version of loadChars doesn't invoke this, so it shouldn't be called
137        log.error("Not using endOfMessage, should not be called");
138        return false;
139    }
140
141    @Override
142    protected void loadChars(AbstractMRReply msg, DataInputStream istream) throws java.io.IOException {
143        byte char1;
144        char1 = readByteProtected(istream);
145        msg.setElement(0, char1 & 0xFE);
146        if ( ((char1 & 0xFF) != 0xFE) && ((char1 & 0xFF) != 0xF8)  && ((char1 & 0xFF) != 0xF9) ) {
147            log.warn("return short message as 1st byte is {}", char1 & 0xFF);
148            return;
149        }
150
151        char1 = readByteProtected(istream);
152        msg.setElement(1, char1 & 0xFF);
153
154        char1 = readByteProtected(istream);
155        msg.setElement(2, char1 & 0xFF);
156    }
157
158    @Override
159    protected void waitForStartOfReply(DataInputStream istream) throws java.io.IOException {
160    }
161
162    /**
163     * No header needed
164     *
165     * @param msg The output byte stream
166     * @return next location in the stream to fill
167     */
168    @Override
169    protected int addHeaderToOutput(byte[] msg, AbstractMRMessage m) {
170        return 0;
171    }
172
173    /**
174     * Add trailer to the outgoing byte stream.
175     *
176     * @param msg    The output byte stream
177     * @param offset the first byte not yet used
178     */
179    @Override
180    protected void addTrailerToOutput(byte[] msg, int offset, AbstractMRMessage m) {
181    }
182
183    /**
184     * Determine how much many bytes the entire message will take, including
185     * space for header and trailer
186     *
187     * @param m The message to be sent
188     * @return Number of bytes for msg (3) plus preceeding NOP (3)
189     */
190    @Override
191    protected int lengthOfByteStream(AbstractMRMessage m) {
192        return 6;
193    }
194
195    /**
196     * Actually transmits the next message to the port
197     */
198    @Override
199    protected void forwardToPort(AbstractMRMessage m, AbstractMRListener reply) {
200        log.debug("forwardToPort message: [{}]", m);
201        // remember who sent this
202        mLastSender = reply;
203
204        // forward the message to the registered recipients,
205        // which includes the communications monitor, except the sender.
206        // Schedule notification via the Swing event queue to ensure order
207        Runnable r = new XmtNotifier(m, mLastSender, this);
208        javax.swing.SwingUtilities.invokeLater(r);
209
210        // stream to port in single write, as that's needed by serial
211        byte msg[] = new byte[lengthOfByteStream(m)];
212        // add header
213        int offset = addHeaderToOutput(msg, m);
214
215        // add data content
216        int len = m.getNumDataElements();
217        for (int i = 0; i < len; i++) {
218            msg[i + offset] = (byte) m.getElement(i);
219        }
220
221        // add trailer
222        addTrailerToOutput(msg, len + offset, m);
223
224        // and stream the bytes
225        try {
226            if (ostream != null) {
227                if (log.isDebugEnabled()) {
228                    StringBuilder f = new StringBuilder("");
229                    for (int i = 0; i < msg.length; i++) {
230                        f.append(Integer.toHexString(0xFF & msg[i])).append(" ");
231                    }
232                    log.debug("write message: {}", f);
233                }
234                while (m.getRetries() >= 0) {
235                    if (portReadyToSend(controller)) {
236                        for (int i = 0; i < len; i++) {
237                            ostream.write(msg[i]);
238                            try {
239                                synchronized (xmtRunnable) {
240                                    xmtRunnable.wait(10);
241                                }
242                            } catch (InterruptedException e) {
243                                Thread.currentThread().interrupt(); // retain if needed later
244                                log.warn("char send wait interrupted");
245                            }
246                        }
247                        break;
248                    } else if (m.getRetries() >= 0) {
249                        log.debug("Retry message: {} attempts remaining: {}", m, m.getRetries());
250                        m.setRetries(m.getRetries() - 1);
251                        try {
252                            synchronized (xmtRunnable) {
253                                xmtRunnable.wait(m.getTimeout());
254                            }
255                        } catch (InterruptedException e) {
256                            log.error("retry wait interrupted");
257                        }
258                    } else {
259                        log.warn("sendMessage: port not ready for data sending: {}", java.util.Arrays.toString(msg));
260                    }
261                }
262            } else {
263                // no stream connected
264                log.warn("sendMessage: no connection established");
265            }
266        } catch (java.io.IOException | RuntimeException e) {
267            log.warn("sendMessage: Exception:", e);
268        }
269    }
270
271    private final static Logger log = LoggerFactory.getLogger(SerialTrafficController.class);
272
273}