001package jmri.jmrix.acela;
002
003import java.io.DataInputStream;
004import jmri.jmrix.AbstractMRListener;
005import jmri.jmrix.AbstractMRMessage;
006import jmri.jmrix.AbstractMRNodeTrafficController;
007import jmri.jmrix.AbstractMRReply;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011/**
012 * Converts Stream-based I/O to/from Acela messages.
013 * <p>
014 * The "SerialInterface" side sends/receives message objects.
015 * <p>
016 * The connection to an AcelaPortController 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 * @see jmri.jmrix.AbstractMRNodeTrafficController
025 *
026 * @author Bob Jacobsen Copyright (C) 2003
027 * @author Bob Jacobsen, Dave Duchamp, multiNode extensions, 2004
028 * @author Bob Coleman Copyright (C) 2007. 2008 Based on CMRI serial example,
029 * modified to establish Acela support.
030 */
031public class AcelaTrafficController extends AbstractMRNodeTrafficController implements AcelaInterface {
032
033    /**
034     * Create a new AcelaTrafficController instance.
035     */
036    public AcelaTrafficController() {
037        super();
038
039        // entirely poll driven, so reduce Polling interval
040        mWaitBeforePoll = 25;  // default = 25
041        setAllowUnexpectedReply(true);
042
043        super.init(0, 1024); // 1024 is an artificial limit but economically reasonable maxNode upper limit
044
045        reallyReadyToPoll = false;           // Need to not start polling until we are ready
046        needToPollNodes = true;              // Need to poll and create corresponding nodes
047        needToInitAcelaNetwork = true;       // Need to poll and create corresponding nodes
048        needToCreateNodesState = 0;          // Need to initialize system and then poll
049        acelaTrafficControllerState = false; //  Flag to indicate which state we are in:
050                                             //  false == Initializing Acela Network
051                                             //  true == Polling Sensors
052    }
053
054    // The methods to implement the AcelaInterface
055
056    @Override
057    public synchronized void addAcelaListener(AcelaListener l) {
058        this.addListener(l);
059    }
060
061    @Override
062    public synchronized void removeAcelaListener(AcelaListener l) {
063        this.removeListener(l);
064    }
065
066    transient int curAcelaNodeIndex = -1;   // cycles over defined nodes when pollMessage is called
067
068    transient private int currentOutputAddress = -1;   // Incremented as Acela Nodes are created and registered
069    // Corresponds to next available output address in nodeArray
070    // Start at -1 to avoid issues with bit address 0
071    transient private int currentSensorAddress = -1;   // Incremented as Acela Nodes are created and registered
072    // Corresponds to next available sensor address in nodeArray
073    // Start at -1 to avoid issues with bit address 0
074
075    private boolean acelaTrafficControllerState = false;    //  Flag to indicate which state we are in: 
076    //  false == Initializing Acela Network
077    //  true == Polling Sensors
078    private boolean reallyReadyToPoll = false;   //  Flag to indicate that we are really ready to poll nodes
079    transient private boolean needToPollNodes = true;   //  Flag to indicate that nodes have not yet been created
080    private boolean needToInitAcelaNetwork = true;   //  Flag to indicate that Acela network must be initialized
081    private int needToCreateNodesState = 0;     //  Need to do a few things:
082    //      Reset Acela Network
083    //      Set Acela Network Online
084    //      Poll for Acela Nodes (and create and register the nodes)
085
086    private boolean acelaSensorsState = false;    //  Flag to indicate whether we have an active sensor and therefore need to poll: 
087    //  false == No active sensor
088    //  true == Active sensor, need to poll sensors
089
090    private int acelaSensorInitCount = 0;     //  Need to count sensors initialized so we know when we can poll them
091
092    private static int SPECIALNODE = 0;         //  Needed to initialize system
093
094    /**
095     * Get minimum address of an Acela node as set on this TrafficController.
096     * @return minimum node address.
097     */
098    public int getMinimumNodeAddress() {
099        return minNode;
100    }
101
102    /**
103     * Get maximum number of Acela nodes as set on this TrafficController.
104     * @return max number of nodes.
105     */
106    public int getMaximumNumberOfNodes() {
107        return maxNode;
108    }
109
110    public boolean getAcelaTrafficControllerState() {
111        return acelaTrafficControllerState;
112    }
113
114    public void setAcelaTrafficControllerState(boolean newstate) {
115        acelaTrafficControllerState = newstate;
116    }
117
118    public synchronized void resetStartingAddresses() {
119        currentOutputAddress = -1;
120        currentSensorAddress = -1;
121    }
122
123    public boolean getAcelaSensorsState() {
124        return acelaSensorsState;
125    }
126
127    public void setAcelaSensorsState(boolean newstate) {
128        acelaSensorsState = newstate;
129    }
130
131    public void incrementAcelaSensorInitCount() {
132        acelaSensorInitCount++;
133        log.debug("Number of Acela sensors initialized: {}", getAcelaSensorInitCount());
134    }
135
136    public int getAcelaSensorInitCount() {
137        return acelaSensorInitCount;
138    }
139
140    public synchronized boolean getNeedToPollNodes() {
141        return needToPollNodes;
142    }
143
144    public synchronized void setNeedToPollNodes(boolean newstate) {
145        needToPollNodes = newstate;
146    }
147
148    public boolean getReallyReadyToPoll() {
149        return reallyReadyToPoll;
150    }
151
152    public void setReallyReadyToPoll(boolean newstate) {
153        log.debug("setting really ready to poll (nodes): {}", newstate);
154        reallyReadyToPoll = newstate;
155    }
156
157    /**
158     * Reference to the system connection memo.
159     */
160    AcelaSystemConnectionMemo mMemo = null;
161
162    /**
163     * Get access to the system connection memo associated with this traffic
164     * controller.
165     *
166     * @return associated systemConnectionMemo object
167     */
168    public AcelaSystemConnectionMemo getSystemConnectionMemo() {
169        return (mMemo);
170    }
171
172    /**
173     * Set the system connection memo associated with this traffic controller.
174     *
175     * @param m associated systemConnectionMemo object
176     */
177    public void setSystemConnectionMemo(AcelaSystemConnectionMemo m) {
178        mMemo = m;
179    }
180
181    /**
182     * Public method to register an Acela node.
183     * @param node which node to register.
184     */
185    public void registerAcelaNode(AcelaNode node) {
186        synchronized (this) {
187            super.registerNode(node);
188
189            // no node validity checking because at this point the node may not be fully defined
190            setMustInit(node, false);  // Do not normally need to init Acela nodes.
191            if (node.getNumOutputBitsPerCard() == 0) {
192                node.setStartingOutputAddress(-1);
193                node.setEndingOutputAddress(-1);
194            } else {
195                if (currentOutputAddress == -1) {  // Need to use -1 to correctly identify bit address 0
196                    currentOutputAddress = 0;
197                }
198                node.setStartingOutputAddress(currentOutputAddress);
199                currentOutputAddress = currentOutputAddress + node.getNumOutputBitsPerCard() - 1;
200                node.setEndingOutputAddress(currentOutputAddress);
201                currentOutputAddress = currentOutputAddress + 1;
202            }
203            if (node.getNumSensorBitsPerCard() == 0) {
204                node.setStartingSensorAddress(-1);
205                node.setEndingSensorAddress(-1);
206            } else {
207                if (currentSensorAddress == -1) {  // Need to use -1 to correctly identify bit address 0
208                    currentSensorAddress = 0;
209                }
210                node.setStartingSensorAddress(currentSensorAddress);
211                currentSensorAddress = currentSensorAddress + node.getNumSensorBitsPerCard() - 1;
212                node.setEndingSensorAddress(currentSensorAddress);
213                currentSensorAddress = currentSensorAddress + 1;
214            }
215        }
216    }
217
218    /**
219     * Public method to set up for initialization of an Acela node.
220     * @param node which node to initialize.
221     */
222    public void initializeAcelaNode(AcelaNode node) {
223        synchronized (this) {
224            setMustInit(node, true);
225            node.initNode();
226        }
227    }
228
229    /**
230     * Public method to identify an AcelaNode from its bit address.
231     * <p>
232     * Note: nodeAddress is numbered from 0
233     *
234     * @param bitAddress address to query.
235     * @param isSensor true to use start sensor address, false to use start output address.
236     * @return '-1' if an AcelaNode with the specified address was not found
237     */
238    public int lookupAcelaNodeAddress(int bitAddress, boolean isSensor) {
239        for (int i = 0; i < getNumNodes(); i++) {
240            AcelaNode node = (AcelaNode) getNode(i);
241            if (isSensor) {
242                if ((bitAddress >= node.getStartingSensorAddress())
243                        && (bitAddress <= node.getEndingSensorAddress())) {
244                    return (i);
245                }
246            } else {
247                if ((bitAddress >= node.getStartingOutputAddress())
248                        && (bitAddress <= node.getEndingOutputAddress())) {
249                    return (i);
250                }
251            }
252        }
253        return (-1);
254    }
255
256    @Override
257    protected AbstractMRMessage enterProgMode() {
258        log.warn("enterProgMode does NOT make sense for Acela serial");
259        return null;
260    }
261
262    @Override
263    protected AbstractMRMessage enterNormalMode() {
264        // can happen during error recovery, null is OK
265        return null;
266    }
267
268    /**
269     * Forward an AcelaMessage to all registered AcelaInterface listeners.
270     * {@inheritDoc}
271     */
272    @Override
273    protected void forwardMessage(AbstractMRListener client, AbstractMRMessage m) {
274        ((AcelaListener) client).message((AcelaMessage) m);
275    }
276
277    /**
278     * Forward an AcelaReply to all registered AcelaInterface listeners.
279     * {@inheritDoc}
280     */
281    @Override
282    protected void forwardReply(AbstractMRListener client, AbstractMRReply m) {
283        ((AcelaListener) client).reply((AcelaReply) m);
284    }
285
286    AcelaSensorManager mSensorManager = null;
287
288    public void setSensorManager(AcelaSensorManager m) {
289        mSensorManager = m;
290    }
291
292    AcelaTurnoutManager mTurnoutManager = null;
293
294    public void setTurnoutManager(AcelaTurnoutManager m) {
295        mTurnoutManager = m;
296    }
297
298    /**
299     * Handle initialization, output and polling for Acela Nodes from within
300     * the running thread.
301     */
302    @Override
303    protected synchronized AbstractMRMessage pollMessage() {
304        // Need to wait until we have read config file
305        if (!reallyReadyToPoll) {
306            return null;
307        }
308
309        if (needToInitAcelaNetwork) {
310            if (needToCreateNodesState == 0) {
311                if (needToPollNodes) {
312                    new AcelaNode(0, AcelaNode.AC,this);
313                    log.info("Created a new Acela Node [0] in order to poll Acela network: " + AcelaNode.AC);
314                }
315                curAcelaNodeIndex = SPECIALNODE;
316                AcelaMessage m = AcelaMessage.getAcelaResetMsg();
317                log.debug("send Acela reset (init step 1) message: {}", m);
318                m.setTimeout(1000);  // wait for init to finish (milliseconds)
319                mCurrentMode = NORMALMODE;
320                needToCreateNodesState++;
321                return m;
322            }
323            if (needToCreateNodesState == 1) {
324                AcelaMessage m = AcelaMessage.getAcelaOnlineMsg();
325                log.debug("send Acela Online (init step 2) message: {}", m);
326                m.setTimeout(1000);  // wait for init to finish (milliseconds)
327                mCurrentMode = NORMALMODE;
328                needToCreateNodesState++;
329                return m;
330            }
331            if (needToPollNodes) {
332                if (needToCreateNodesState == 2) {
333                    AcelaMessage m = AcelaMessage.getAcelaPollNodesMsg();
334                    log.debug("send Acela poll nodes message: {}", m);
335                    m.setTimeout(100);  // wait for init to finish (milliseconds)
336                    mCurrentMode = NORMALMODE;
337                    needToInitAcelaNetwork = false;
338                    needToPollNodes = false;
339                    return m;
340                }
341            } else {
342                needToInitAcelaNetwork = false;
343                setAcelaTrafficControllerState(true);
344            }
345        }
346
347        // ensure validity of call
348        if (getNumNodes() <= 0) {
349            return null;
350        }
351
352        // move to a new node
353        curAcelaNodeIndex++;
354        if (curAcelaNodeIndex >= getNumNodes()) {
355            curAcelaNodeIndex = 0;
356        }
357
358        // ensure that each node is initialized
359        AcelaNode node = (AcelaNode) getNode(curAcelaNodeIndex);
360        if (node.hasActiveSensors) {
361            for (int s = 0; s < node.sensorbitsPerCard; s++) {
362                if (node.sensorNeedInit[s] && !node.sensorHasBeenInit[s]) {
363                    AcelaMessage m = AcelaMessage.getAcelaConfigSensorMsg();
364                    int tempiaddr = s + node.getStartingSensorAddress();
365                    byte tempbaddr = (byte) (tempiaddr);
366                    m.setElement(2, tempbaddr);
367                    m.setElement(3, node.sensorConfigArray[s]);
368                    log.debug("send Acela Config Sensor message: {}", m);
369                    incrementAcelaSensorInitCount();
370                    m.setTimeout(100);  // wait for init to finish (milliseconds)
371                    mCurrentMode = NORMALMODE;
372                    node.sensorHasBeenInit[s] = true;
373                    node.sensorNeedInit[s] = false;
374                    return m;
375                }
376            }
377        }
378
379        // send Output packet if needed
380        if (getNode(curAcelaNodeIndex).mustSend()) {
381            getNode(curAcelaNodeIndex).resetMustSend();
382            AbstractMRMessage m = getNode(curAcelaNodeIndex).createOutPacket();
383            m.setTimeout(100);  // no need to wait for output to answer
384            log.debug("request write command to send: {}", m);
385            mCurrentMode = NORMALMODE;
386            return m;
387        }
388
389        // Trying to serialize Acela initialization so system is stable
390        // So we will not poll sensors or send om/off commands until we have
391        // initialized all of the sensor modules -- this can take several seconds
392        // during a cold system startup.
393        if ((currentSensorAddress == 0) || (currentSensorAddress != getAcelaSensorInitCount())) {
394            return null;
395        }
396
397        if (acelaSensorsState) {    //  Flag to indicate whether we have an active sensor and therefore need to poll
398            AcelaMessage m = AcelaMessage.getAcelaPollSensorsMsg();
399            log.debug("send Acela poll sensors message: {}", m);
400            m.setTimeout(100);  // wait for init to finish (milliseconds)
401            mCurrentMode = NORMALMODE;
402            return m;
403        } else {
404            // no Sensors (inputs) are active for this node
405            return null;
406        }
407    }
408
409    @Override
410    protected synchronized void handleTimeout(AbstractMRMessage m, AbstractMRListener l) {
411        // don't use super behavior, as timeout to init, transmit message is normal
412        // inform node, and if it resets then reinitialize        
413        if (getNode(curAcelaNodeIndex).handleTimeout(m, l)) {
414            setMustInit(curAcelaNodeIndex, true);
415        }
416    }
417
418    @Override
419    protected synchronized void resetTimeout(AbstractMRMessage m) {
420        // don't use super behavior, as timeout to init, transmit message is normal
421        // and inform node
422        getNode(curAcelaNodeIndex).resetTimeout(m);
423    }
424
425    @Override
426    protected AbstractMRListener pollReplyHandler() {
427        return mSensorManager;
428    }
429
430    /**
431     * Forward a pre-formatted message to the actual interface.
432     * {@inheritDoc}
433     */
434    @Override
435    public void sendAcelaMessage(AcelaMessage m, AcelaListener reply) {
436        sendMessage(m, reply);
437    }
438
439    @Override
440    protected AbstractMRReply newReply() {
441        return new AcelaReply();
442    }
443
444    @Override
445    protected boolean endOfMessage(AbstractMRReply msg) {
446        // our version of loadChars doesn't invoke this, so it shouldn't be called
447        return true;
448    }
449
450    @Override
451    protected void loadChars(AbstractMRReply msg, DataInputStream istream) throws java.io.IOException {
452        int char1 = readByteProtected(istream)&0xFF;
453        if (char1 == 0x00) {  // 0x00 means command processed OK.
454            msg.setElement(0, char1);
455            //  0x01 means that the Acela network is offline
456            //  0x02 means that an illegal address was sent
457            //  0x03 means that an illegal command was sent
458            //  For now we are not going to check for these
459            //  three conditions since they will only catch
460            //  programming errors (versus runtime errors)
461            //  and the checking may mess up the polling replies.
462
463        } else {
464            if ((char1 == 0x81) || (char1 == 0x82)) {
465                //  0x81 means that a sensor has changed.
466                //  0x82 means that communications has been lost
467                //  For now we will check for these two 
468                //  conditions since they do represent
469                //  runtime errors at the risk that in a very very
470                //  large Acela network the checking may mess
471                //  up the polling replies.
472                msg.setElement(0, char1);
473            } else {
474                //  We have a reply to a poll (either pollnodes 
475                //  or pollsensors).  The first byte will be the
476                //  length of the reply followed by the
477                //  indicated number of bytes.
478                //
479                //  For now we will send the reply to the sensor
480                //  manager.  In the future we should really have
481                //  an Acela Network Manager and an Acela Sensor
482                //  Manager -- but, for now, we 'know' which state
483                //  we are in.
484                for (int i = 0; i < char1; i++) {
485                    byte charn = readByteProtected(istream);
486                    msg.setElement(i, charn);
487                }
488            }
489        }
490    }
491
492    @Override
493    protected void waitForStartOfReply(DataInputStream istream) throws java.io.IOException {
494        // Just return
495    }
496
497    /**
498     * For each sensor node call markChanges.
499     * @param r reply to use in sensor update.
500     */
501    public void updateSensorsFromPoll(AcelaReply r) {
502        for (int i = 0; i < getNumNodes(); i++) {
503            AcelaNode node = (AcelaNode) getNode(i);
504            if (node.getSensorBitsPerCard() > 0) {
505                node.markChanges(r);
506            }
507        }
508    }
509
510    private final static Logger log = LoggerFactory.getLogger(AcelaTrafficController.class);
511
512}