001package jmri.jmrix.maple;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import java.io.DataInputStream;
005import jmri.jmrix.AbstractMRListener;
006import jmri.jmrix.AbstractMRMessage;
007import jmri.jmrix.AbstractMRNodeTrafficController;
008import jmri.jmrix.AbstractMRReply;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012/**
013 * Converts Stream-based I/O to/from Maple serial messages.
014 * <p>
015 * The "SerialInterface" side sends/receives message objects.
016 * <p>
017 * The connection to a SerialPortController is via a pair of *Streams, which
018 * then carry sequences of characters for transmission. Note that this
019 * processing is handled in an independent thread.
020 * <p>
021 * This handles the state transitions, based on the necessary state in each
022 * message.
023 * <p>
024 * Handles initialization, polling, output, and input for multiple Serial Nodes.
025 *
026 * @author Bob Jacobsen Copyright (C) 2003, 2008
027 * @author Bob Jacobsen, Dave Duchamp, multiNode extensions, 2004
028 * @author Bob Jacobsen, Dave Duchamp, adapt to use for Maple 2008, 2009, 2010
029 *
030 * @since 2.3.7
031 */
032@SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC", justification = "multiple variables accessed outside synchronized core, which is quite suspicious, but code seems to interlock properly")
033public class SerialTrafficController extends AbstractMRNodeTrafficController implements SerialInterface {
034
035    /**
036     * Create a new Maple SerialTrafficController instance.
037     */
038    public SerialTrafficController() {
039        super();
040
041        // set node range
042        init(0, 127);
043
044        // entirely poll driven, so reduce interval
045        mWaitBeforePoll = 5;  // default = 25
046
047        // initialize input and output utility classes
048        mInputBits = new InputBits(this);
049        mOutputBits = new OutputBits(this);
050    }
051
052    // InputBits and OutputBits
053    private InputBits mInputBits = null;
054    private OutputBits mOutputBits = null;
055
056    public InputBits inputBits(){
057      return mInputBits;
058    }
059
060    public OutputBits outputBits(){
061      return mOutputBits;
062    }
063
064    // The methods to implement the SerialInterface
065
066    @Override
067    public synchronized void addSerialListener(SerialListener l) {
068        this.addListener(l);
069    }
070
071    @Override
072    public synchronized void removeSerialListener(SerialListener l) {
073        this.removeListener(l);
074    }
075
076    /**
077     * Public method to set up for initialization of a Serial node.
078     * @param node unused.
079     */
080    public void initializeSerialNode(SerialNode node) {
081        // dummy routine - Maple System devices do not require initialization
082    }
083
084    @Override
085    protected AbstractMRMessage enterProgMode() {
086        log.warn("enterProgMode doesn't make sense for Maple serial");
087        return null;
088    }
089
090    @Override
091    protected AbstractMRMessage enterNormalMode() {
092        // can happen during error recovery, null is OK
093        return null;
094    }
095
096    /**
097     * Forward a SerialMessage to all registered SerialInterface listeners.
098     */
099    @Override
100    protected void forwardMessage(AbstractMRListener client, AbstractMRMessage m) {
101        ((SerialListener) client).message((SerialMessage) m);
102    }
103
104    /**
105     * Forward a SerialReply to all registered SerialInterface listeners.
106     */
107    @Override
108    protected void forwardReply(AbstractMRListener client, AbstractMRReply m) {
109        ((SerialListener) client).reply((SerialReply) m);
110    }
111
112    SerialSensorManager mSensorManager = null;
113
114    public void setSensorManager(SerialSensorManager m) {
115        mSensorManager = m;
116    }
117
118    // initialization not needed ever
119    @Override
120    protected boolean getMustInit(int i) {
121        return false;
122    }
123
124    // With the Maple Systems Protocol, output packets are limited to 99 bits.  If there are more than
125    // 99 bits configured, multiple output packets must be sent.  The following cycle through that
126    // process.
127    private boolean mNeedSend = true;
128    private int mStartBitNumber = 1;
129    // Similarly the poll command can only poll 99 input bits at a time, so more packets may be needed.
130    private boolean mNeedAdditionalPollPacket = false;
131    private int mStartPollAddress = 1;
132    // The Maple poll response does not contain an address, so the following is needed.
133    private int mSavedPollAddress = 1;
134
135    public int getSavedPollAddress() {
136        return mSavedPollAddress;
137    }
138    private int mCurrentNodeIndexInPoll = -1;
139
140    /**
141     * Handle output and polling for Maple Serial Nodes from within the running
142     * thread.
143     */
144    @Override
145    protected synchronized AbstractMRMessage pollMessage() {
146        // ensure validity of call - are nodes in yet?
147        if (getNumNodes() <= 0) {
148            return null;
149        }
150        if (curSerialNodeIndex >= getNumNodes()) {
151            curSerialNodeIndex = 0;
152            // process input bits
153            mInputBits.makeChanges();
154            // initialize send of output bits
155            mNeedSend = true;
156            mStartBitNumber = 1;
157        }
158        // send Output packet if needed
159        if (mNeedSend) {
160            int endBitNumber = mStartBitNumber + 98;
161            if (endBitNumber > OutputBits.getNumOutputBits()) {
162                endBitNumber = OutputBits.getNumOutputBits();
163                mNeedSend = false;
164            }
165            if (endBitNumber == OutputBits.getNumOutputBits()) {
166                mNeedSend = false;
167            }
168            SerialMessage m = mOutputBits.createOutPacket(mStartBitNumber, endBitNumber);
169            mCurrentNodeIndexInPoll = -1;
170
171            // update the starting bit number if additional packets are needed
172            if (mNeedSend) {
173                mStartBitNumber = endBitNumber + 1;
174            }
175            return m;
176        }
177        // poll for Sensor input
178        int count = 99;
179        if (count > (InputBits.getNumInputBits() - mStartPollAddress + 1)) {
180            count = InputBits.getNumInputBits() - mStartPollAddress + 1;
181        }
182        SerialMessage m = SerialMessage.getPoll(
183                getNode(curSerialNodeIndex).getNodeAddress(), mStartPollAddress, count);
184        mSavedPollAddress = mStartPollAddress;
185        mCurrentNodeIndexInPoll = curSerialNodeIndex;
186
187        // check if additional packet is needed
188        if ((mStartPollAddress + count - 1) < InputBits.getNumInputBits()) {
189            mNeedAdditionalPollPacket = true;
190            mStartPollAddress = mStartPollAddress + 99;
191        } else {
192            mNeedAdditionalPollPacket = false;
193            mStartPollAddress = 1;
194            curSerialNodeIndex++;
195        }
196        return m;
197    }
198
199    protected int wrTimeoutCount = 0;
200
201    public int getWrTimeoutCount() {
202        return wrTimeoutCount;
203    }
204
205    public void resetWrTimeoutCount() {
206        wrTimeoutCount = 0;
207    }
208
209    @Override
210    protected void handleTimeout(AbstractMRMessage m, AbstractMRListener l) {
211        if (m.getElement(3) == 'W' && m.getElement(4) == 'C') {
212            wrTimeoutCount++;    // should not happen
213        } else if (m.getElement(3) == 'R' && m.getElement(4) == 'C') {
214            if (mNeedAdditionalPollPacket) {
215                // log.warn("Timeout of poll message, node = {} beg addr = {}", curSerialNodeIndex, mSavedPollAddress);
216                getNode(curSerialNodeIndex).handleTimeout(m, l);
217            } else {
218                // log.warn("Timeout of poll message, node = {} beg addr = {}", (curSerialNodeIndex-1), mSavedPollAddress);
219                getNode(curSerialNodeIndex - 1).handleTimeout(m, l);
220            }
221        } else {
222            log.error("Timeout of unknown message - {}", m.toString());
223        }
224    }
225
226    @Override
227    protected void resetTimeout(AbstractMRMessage m) {
228        if (mCurrentNodeIndexInPoll < 0) {
229            wrTimeoutCount = 0;    // should never happen - outputs should not be timed
230        } else {
231            // don't use super behavior, as timeout to init, transmit message is normal
232            // inform node
233            getNode(mCurrentNodeIndexInPoll).resetTimeout(m);
234        }
235    }
236
237    @Override
238    protected AbstractMRListener pollReplyHandler() {
239        return mSensorManager;
240    }
241
242    /**
243     * Forward a preformatted message to the actual interface.
244     */
245    @Override
246    public void sendSerialMessage(SerialMessage m, SerialListener reply) {
247        sendMessage(m, reply);
248    }
249
250    @Override
251    protected AbstractMRReply newReply() {
252        return new SerialReply();
253    }
254
255    @Override
256    protected boolean endOfMessage(AbstractMRReply msg) {
257        // our version of loadChars doesn't invoke this, so it shouldn't be called
258        log.error("Not using endOfMessage, should not be called");
259        return false;
260    }
261
262    @Override
263    public void loadChars(AbstractMRReply msg, DataInputStream istream) throws java.io.IOException {
264        int i;
265        boolean first = true;
266        for (i = 0; i < msg.maxSize() - 1; i++) {
267            byte char1 = readByteProtected(istream);
268            msg.setElement(i, char1 & 0xFF);
269            if (first) {
270                first = false;
271                log.debug("start message with {}", char1);
272            }
273            if (char1 == 0x03) { // normal message
274                // get checksum bytes and end
275                log.debug("ETX ends message");
276                char1 = readByteProtected(istream);
277                msg.setElement(i + 1, char1 & 0xFF);
278                char1 = readByteProtected(istream);
279                msg.setElement(i + 2, char1 & 0xFF);
280                break;           // end of message
281            }
282            if (char1 == 0x06) { // ACK OK
283                // get station, command and end
284                log.debug("ACK ends message");
285                char1 = readByteProtected(istream);  // byte 2
286                msg.setElement(++i, char1 & 0xFF);
287                char1 = readByteProtected(istream);  // byte 3
288                msg.setElement(++i, char1 & 0xFF);
289                char1 = readByteProtected(istream);  // byte 4
290                msg.setElement(++i, char1 & 0xFF);
291                char1 = readByteProtected(istream);  // byte 5
292                msg.setElement(++i, char1 & 0xFF);
293                break;           // end of message
294            }
295            if (char1 == 0x15) { // NAK error
296                // get station, command, error bytes and end
297                log.debug("NAK ends message");
298                char1 = readByteProtected(istream);  // byte 2
299                msg.setElement(++i, char1 & 0xFF);
300                char1 = readByteProtected(istream);  // byte 3
301                msg.setElement(++i, char1 & 0xFF);
302                char1 = readByteProtected(istream);  // byte 4
303                msg.setElement(++i, char1 & 0xFF);
304                char1 = readByteProtected(istream);  // byte 5
305                msg.setElement(++i, char1 & 0xFF);
306                char1 = readByteProtected(istream);  // byte 6
307                msg.setElement(++i, char1 & 0xFF);
308                break;           // end of message
309            }
310        }
311    }
312
313    @Override
314    protected void waitForStartOfReply(DataInputStream istream) throws java.io.IOException {
315        // don't skip anything
316    }
317
318    /**
319     * Add header to the outgoing byte stream.
320     *
321     * @param msg the output byte stream
322     * @param m the message to add the header to
323     * @return next location in the stream to fill
324     */
325    @Override
326    protected int addHeaderToOutput(byte[] msg, AbstractMRMessage m) {
327        return 0;
328    }
329
330    /**
331     * Add trailer to the outgoing byte stream.
332     *
333     * @param msg    The output byte stream
334     * @param offset the first byte not yet used
335     */
336    @Override
337    protected void addTrailerToOutput(byte[] msg, int offset, AbstractMRMessage m) {
338    }
339
340    /**
341     * Determine how much many bytes the entire message will take, including
342     * space for header and trailer.
343     *
344     * @param m the message to be sent
345     * @return Number of bytes
346     */
347    @Override
348    protected int lengthOfByteStream(AbstractMRMessage m) {
349        int len = m.getNumDataElements();
350        return len;
351    }
352
353    private final static Logger log = LoggerFactory.getLogger(SerialTrafficController.class);
354
355}