001package jmri.jmrix.grapevine;
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 * Convert Stream-based I/O to/from Grapevine 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 SerialNodes.
025 *
026 * @author Bob Jacobsen Copyright (C) 2003, 2006, 2008
027 * @author Bob Jacobsen, Dave Duchamp, multiNode extensions, 2004
028 */
029public class SerialTrafficController extends AbstractMRNodeTrafficController implements SerialInterface {
030
031    /**
032     * Create a new TrafficController instance.
033     *
034     * @param adaptermemo the associated SystemConnectionMemo
035     */
036    public SerialTrafficController(GrapevineSystemConnectionMemo adaptermemo) {
037        super();
038        mMemo = adaptermemo;
039        log.debug("creating a new GrapevineTrafficController object on {}", adaptermemo.getSystemPrefix());
040        logDebug = log.isDebugEnabled();
041
042        // set node range
043        init(0, 255);
044
045        // not polled at all, so allow unexpected messages, and
046        // use poll delay just to spread out startup
047        setAllowUnexpectedReply(true);
048        mWaitBeforePoll = 100;  // default = 25
049    }
050
051    // have several debug statements in tight loops, e.g. every character;
052    // only want to check once
053    boolean logDebug = false;
054
055    /**
056     * Get minimum address of an Grapevine node as set on this TrafficController.
057     * @return minimum node address.
058     */
059    public int getMinimumNodeAddress() {
060        return minNode;
061    }
062
063    // The methods to implement the SerialInterface
064
065    /**
066     * Make connection to existing PortController (adapter) object.
067     *
068     * @param p the Adapter we're connecting to
069     */
070    public void connectPort(SerialPortController p) {
071        if (controller != null) {
072            log.warn("connectPort called when already connected");
073        } else {
074            log.debug("connectPort invoked");
075        }
076        //controller = p;
077        super.connectPort(p);
078    }
079
080    @Override
081    public synchronized void addSerialListener(SerialListener l) {
082        this.addListener(l);
083    }
084
085    @Override
086    public synchronized void removeSerialListener(SerialListener l) {
087        this.removeListener(l);
088    }
089
090    /**
091     * Set up for initialization of a Serial node.
092     * @param node node to initialize.
093     */
094    public void initializeSerialNode(SerialNode node) {
095        synchronized (this) {
096            // find the node in the registered node list
097            for (int i = 0; i < getNumNodes(); i++) {
098                if (getNode(i) == node) {
099                    // found node - set up for initialization
100                    setMustInit(i, true);
101                    return;
102                }
103            }
104        }
105    }
106
107    @Override
108    protected AbstractMRMessage enterProgMode() {
109        log.warn("enterProgMode doesn't make sense for Grapevine Serial");
110        return null;
111    }
112
113    @Override
114    protected AbstractMRMessage enterNormalMode() {
115        return null;
116    }
117
118    /**
119     * Forward a SerialMessage to all registered SerialInterface listeners.
120     */
121    @Override
122    protected void forwardMessage(AbstractMRListener client, AbstractMRMessage m) {
123        ((SerialListener) client).message((SerialMessage) m);
124    }
125
126    /**
127     * Forward a SerialReply to all registered SerialInterface listeners.
128     */
129    @Override
130    protected void forwardReply(AbstractMRListener client, AbstractMRReply m) {
131        ((SerialListener) client).reply((SerialReply) m);
132    }
133
134    SerialSensorManager mSensorManager = null;
135
136    public void setSensorManager(SerialSensorManager m) {
137        mSensorManager = m;
138        // also register this to be notified
139        addSerialListener(m);
140    }
141
142    /**
143     * Handle initialization, output and polling for Grapevine from within the
144     * running thread.
145     */
146    @Override
147    protected synchronized AbstractMRMessage pollMessage() {
148        // ensure validity of call
149        if (getNumNodes() <= 0) {
150            return null;
151        }
152
153        // move to a new node
154        curSerialNodeIndex++;
155        if (curSerialNodeIndex >= getNumNodes()) {
156            curSerialNodeIndex = 0;
157        }
158        // ensure that each node is initialized        
159        if (getMustInit(curSerialNodeIndex)) {
160            setMustInit(curSerialNodeIndex, false);
161            SerialMessage m = (SerialMessage) (getNode(curSerialNodeIndex).createInitPacket());
162            if (m != null) {
163                log.debug("send init message: {} to node {}", m.toString(), curSerialNodeIndex);
164                m.setTimeout(50);  // wait for init to finish (milliseconds)
165                return m;
166            }   // else fall through to continue
167        }
168        /*         // send Output packet if needed */
169        /*         if (nodeArray[curSerialNodeIndex].mustSend()) { */
170        /*             log.debug("request write command to send"); */
171        /*             SerialMessage m = nodeArray[curSerialNodeIndex].createOutPacket(); */
172        /*             nodeArray[curSerialNodeIndex].resetMustSend(); */
173        /*             m.setTimeout(500); */
174        /*             return m; */
175        /*         } */
176        /*         // poll for Sensor input */
177        /*         if ( nodeArray[curSerialNodeIndex].getSensorsActive() ) { */
178        /*             // Some sensors are active for this node, issue poll */
179        /*             SerialMessage m = SerialMessage.getPoll( */
180        /*                                 nodeArray[curSerialNodeIndex].getNodeAddress()); */
181        /*             if (curSerialNodeIndex>=numNodes) curSerialNodeIndex = 0; */
182        /*             return m; */
183        /*         } */
184        /*         else { */
185        /*             // no Sensors (inputs) are active for this node */
186        /*             return null; */
187        /*         } */
188        return null;
189    }
190
191    @Override
192    protected synchronized void handleTimeout(AbstractMRMessage m, AbstractMRListener l) {
193        // inform node, and if it resets then reinitialize 
194        if (getNode(curSerialNodeIndex) != null) {
195            if (getNode(curSerialNodeIndex).handleTimeout(m, l)) {
196                setMustInit(curSerialNodeIndex, true);
197            } else {
198                log.warn("Timeout can't be handled due to missing node (index {})", curSerialNodeIndex);
199            }
200        }
201    }
202
203    @Override
204    protected synchronized void resetTimeout(AbstractMRMessage m) {
205        // inform node
206        getNode(curSerialNodeIndex).resetTimeout(m);
207    }
208
209    @Override
210    protected AbstractMRListener pollReplyHandler() {
211        return mSensorManager;
212    }
213
214    /**
215     * Forward a preformatted message to the actual interface.
216     */
217    @Override
218    public void sendSerialMessage(SerialMessage m, SerialListener reply) {
219        if (m == null) {
220            log.debug("empty message");
221            return;
222        }
223        log.debug("Grapevine SerialTrafficController sendMessage() {}", m.toString());
224        sendMessage(m, reply);
225    }
226
227    /**
228     * Reference to the system connection memo.
229     */
230    GrapevineSystemConnectionMemo mMemo = null;
231
232    /**
233     * Get access to the system connection memo associated with this traffic
234     * controller.
235     *
236     * @return associated systemConnectionMemo object
237     */
238    public GrapevineSystemConnectionMemo getSystemConnectionMemo() {
239        return mMemo;
240    }
241
242    /**
243     * Set the system connection memo associated with this traffic controller.
244     *
245     * @param m associated systemConnectionMemo object
246     */
247    public void setSystemConnectionMemo(GrapevineSystemConnectionMemo m) {
248        log.debug("GrapevineTrafficController set memo to {}", m.getUserName());
249        mMemo = m;
250    }
251
252    @Override
253    protected AbstractMRReply newReply() {
254        return new SerialReply();
255    }
256
257    @Override
258    protected boolean endOfMessage(AbstractMRReply msg) {
259        // our version of loadChars doesn't invoke this, so it shouldn't be called
260        log.error("Not using endOfMessage, should not be called");
261        return false;
262    }
263
264    protected int currentAddr = -1; // at startup, can't match
265
266    int nextReplyLen = 4;
267
268    @Override
269    protected void forwardToPort(AbstractMRMessage m, AbstractMRListener reply) {
270        nextReplyLen = ((SerialMessage) m).getReplyLen();
271        super.forwardToPort(m, reply);
272    }
273
274    byte[] buffer = new byte[4];
275    int state = 0;
276
277    @Override
278    protected void loadChars(AbstractMRReply msg, DataInputStream istream) throws java.io.IOException {
279        while (doNextStep(msg, istream)) {
280        }
281    }
282
283    /**
284     * Execute a state machine to parse messages from the input characters. May
285     * consume one or more than one character.
286     *
287     * @param msg Message to parse
288     * @param istream Source of data
289     * @return true when the message has been completely loaded
290     * @throws java.io.IOException from underlying operation
291     */
292    @SuppressWarnings("fallthrough")
293    @SuppressFBWarnings(value = "SF_SWITCH_FALLTHROUGH")
294    boolean doNextStep(AbstractMRReply msg, DataInputStream istream) throws java.io.IOException {
295        switch (state) {
296            case 0:
297                // get 1st char, check for address bit
298                buffer[0] = readByteProtected(istream);
299                log.debug("state 0, rcv {}", (buffer[0] & 0xFF));
300                if ((buffer[0] & 0x80) == 0) {
301                    log.warn("1st byte not address: {}", (buffer[0] & 0xFF));
302                    return true;  // try again with next
303                }
304                state = 1;
305            // and continue anyway
306            case 1:
307                buffer[1] = readByteProtected(istream);
308                log.debug("state 1, rcv {}", (buffer[1] & 0xFF));
309                if ((buffer[1] & 0x80) != 0) {
310                    buffer[0] = buffer[1];
311                    state = 1; // use this as address and try again
312                    log.warn("2nd byte HOB set: {}, going to state 1", (buffer[1] & 0xFF));
313                    return true;
314                }
315                state = 2;
316            // fall through
317            case 2:
318                // as a special case, see what happens if a short
319                // message is expected
320                if (nextReplyLen == 2) {
321                    // we'll accept these two bytes as a reply
322                    buffer[2] = 0;
323                    buffer[3] = 0;
324                    loadBuffer(msg);
325                    ((SerialReply) msg).setNumDataElements(2); // flag short reply
326                    nextReplyLen = 4; // only happens once
327                    state = 0;
328                    log.debug("Short message complete: {}", msg.toString());
329                    return false;  // have received a message
330                }
331                // here for normal four byte message expected
332                buffer[2] = readByteProtected(istream);
333                log.debug("state 2, rcv {}", (buffer[2] & 0xFF));
334                if (buffer[0] != buffer[2]) {
335                    // no match, consider buffer[2] start of new message
336                    log.warn("addresses don't match: {}, {}. going to state 1", (buffer[0] & 0xFF), (buffer[2] & 0xFF));
337                    buffer[0] = buffer[2];
338                    state = 1;
339                    return true;
340                }
341                state = 3;
342            // fall through
343            case 3:
344                buffer[3] = readByteProtected(istream);
345                log.debug("state 3, rcv {}", (buffer[3] & 0xFF));
346                if ((buffer[3] & 0x80) != 0) { // unexpected high bit
347                    buffer[0] = buffer[3];
348                    state = 1; // use this as address and try again
349                    log.warn("3rd byte HOB set: {}, going to state 1", (buffer[3] & 0xFF));
350                    return true;
351                }
352                // Check for "software version" command, error message; special
353                // cases with deliberately bad parity
354                boolean pollMsg = ((buffer[1] == buffer[3]) && (buffer[1] == 119));
355                boolean errMsg = ((buffer[0] & 0xFF) == 0x80);
356
357                // check 'parity'
358                int parity = (buffer[0] & 0xF) + ((buffer[0] & 0x70) >> 4)
359                        + ((buffer[1] * 2) & 0xF) + (((buffer[1] * 2) & 0xF0) >> 4)
360                        + (buffer[3] & 0xF) + ((buffer[3] & 0x70) >> 4);
361                if (((parity & 0xF) != 0) && !pollMsg && !errMsg) {
362                    log.warn("parity mismatch: {}, going to state 2 with content {}, {}", parity, (buffer[2] & 0xFF), (buffer[3] & 0xFF));
363                    buffer[0] = buffer[2];
364                    buffer[1] = buffer[3];
365                    state = 2;
366                    return true;
367                }
368                // success!
369                loadBuffer(msg);
370                log.debug("Message complete: {}", msg.toString());
371                state = 0;
372                return false;
373            default:
374                log.error("unexpected loadChars state: {}. go direct to state 0", state);
375                state = 0;
376                return true;
377        }
378    }
379
380    void loadBuffer(AbstractMRReply msg) {
381        msg.setElement(0, buffer[0]);
382        msg.setElement(1, buffer[1]);
383        msg.setElement(2, buffer[2]);
384        msg.setElement(3, buffer[3]);
385    }
386
387    @Override
388    protected void waitForStartOfReply(DataInputStream istream) throws java.io.IOException {
389        // does nothing
390    }
391
392    /**
393     * Add header to the outgoing byte stream.
394     *
395     * @param msg The output byte stream
396     * @return next location in the stream to fill
397     */
398    @Override
399    protected int addHeaderToOutput(byte[] msg, AbstractMRMessage m) {
400        return 0;  // Do nothing
401    }
402
403    /**
404     * Although this protocol doesn't use a trailer, we implement this method to
405     * set the expected reply address for this message.
406     *
407     * @param msg    The output byte stream
408     * @param offset the first byte not yet used
409     * @param m      the original message
410     */
411    @Override
412    protected void addTrailerToOutput(byte[] msg, int offset, AbstractMRMessage m) {
413        currentAddr = ((SerialMessage) m).getAddr();
414        return;
415    }
416
417    /**
418     * Determine how much many bytes the entire message will take, including
419     * space for header and trailer
420     *
421     * @param m The message to be sent
422     * @return Number of bytes
423     */
424    @Override
425    protected int lengthOfByteStream(AbstractMRMessage m) {
426        return m.getNumDataElements(); // All are same length as message
427    }
428
429    private final static Logger log = LoggerFactory.getLogger(SerialTrafficController.class);
430
431}