001package jmri.jmrix.lenz;
002
003import jmri.Consist;
004import jmri.LocoAddress;
005import jmri.DccLocoAddress;
006import jmri.implementation.AbstractConsistManager;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010/**
011 * Consist Manager for use with the XNetConsist class for the consists it builds
012 * @author Paul Bender Copyright (C) 2004-2010
013 * @navassoc 1 - * jmri.jmrix.lenz.XNetConsist
014 */
015public class XNetConsistManager extends AbstractConsistManager {
016
017    protected XNetTrafficController tc;
018    private boolean requestingUpdate = false;
019
020    /**
021     * Constructor - call the constructor for the superclass, and initialize the
022     * consist reader thread, which retrieves consist information from the
023     * command station.
024     * @param systemMemo system connection.
025     */
026    public XNetConsistManager(XNetSystemConnectionMemo systemMemo) {
027        super();
028        tc = systemMemo.getXNetTrafficController();
029        this.systemMemo = systemMemo;
030    }
031    final XNetSystemConnectionMemo systemMemo;
032
033    /**
034     * This implementation does command station consists, so return true.
035     */
036    @Override
037    public boolean isCommandStationConsistPossible() {
038        return true;
039    }
040
041    /**
042     * Does a CS consist require a separate consist address? CS consist
043     * addresses are assigned by the command station, so no consist address is
044     * needed, so return false.
045     */
046    @Override
047    public boolean csConsistNeedsSeperateAddress() {
048        return false;
049    }
050
051    /**
052     * Add a new XNetConsist with the given address to consistTable/consistList.
053     */
054    @Override
055    public Consist addConsist(LocoAddress address) {
056        if (! (address instanceof DccLocoAddress)) {
057            throw new IllegalArgumentException("address is not a DccLocoAddress object");
058        }
059        if (consistTable.containsKey(address)) { // no duplicates allowed.
060            return consistTable.get(address);
061        }
062        XNetConsist consist;
063        consist = new XNetConsist((DccLocoAddress) address, tc, systemMemo);
064        consistTable.put(address, consist);
065        return (consist);
066    }
067
068    /**
069     * Request an update from the layout, loading Consists from the command
070     * station.
071     */
072    @Override
073    public void requestUpdateFromLayout() {
074        if (shouldRequestUpdateFromLayout()) {
075            // Initilize the consist reader.
076            new XNetConsistReader();
077        }
078    }
079
080    @Override
081    protected boolean shouldRequestUpdateFromLayout() {
082        return !requestingUpdate;
083    }
084
085    /**
086     * Internal class to read consists from the Command Station.
087     */
088    private class XNetConsistReader implements XNetListener {
089
090        // Storage for addresses
091        int _lastMUAddress = 0;
092        int _lastAddress = 0;
093        int _lastMemberAddress = 0;
094        XNetConsist currentConsist = null;
095        // Possible States
096        static final int IDLE = 0;
097        static final int SEARCHREQUESTSENT = 1;
098        static final int MUSEARCHSENT = 2;
099        static final int MUINFOREQUESTSENT = 4;
100        static final int DHADDRESS1INFO = 8;
101        static final int DHADDRESS2INFO = 16;
102        // Current State
103        int currentState = IDLE;
104
105        XNetConsistReader() {
106            // Register as an XPressNet Listener
107            tc.addXNetListener(XNetInterface.COMMINFO
108                    | XNetInterface.THROTTLE
109                    | XNetInterface.CONSIST,
110                    this);
111            searchNext();
112        }
113
114        private void searchNext() {
115            requestingUpdate = true;
116            log.debug("Sending search for next DB Entry, _lastAddress is: {}",_lastAddress);
117            currentState = SEARCHREQUESTSENT;
118            XNetMessage msg = XNetMessage.getNextAddressOnStackMsg(_lastAddress, true);
119            tc.sendXNetMessage(msg, this);
120        }
121
122        private void searchMU() {
123            log.debug("Sending search for next MU Entry, _lastMUAddress is: {} _lastMemberAddress is: {}",_lastMUAddress,_lastMemberAddress);
124            currentState = MUSEARCHSENT;
125            XNetMessage msg = XNetMessage.getDBSearchMsgNextMULoco(_lastMUAddress, _lastMemberAddress, true);
126            tc.sendXNetMessage(msg, this);
127        }
128
129        private void requestInfoMU() {
130            log.debug("Sending search for next MU Entry information , _lastMemberAddress is: {}",_lastMemberAddress);
131            currentState = MUINFOREQUESTSENT;
132            XNetMessage msg = XNetMessage.getLocomotiveInfoRequestMsg(_lastMemberAddress);
133            tc.sendXNetMessage(msg, this);
134        }
135
136        // Listener for messages from the command station
137        @Override
138        public void message(XNetReply l) {
139            switch (currentState) {
140                case SEARCHREQUESTSENT:
141                    // We sent a request to search the stack.
142                    // We need to find out what type of message
143                    // was recived as a response.  If We're
144                    // told the message is for an MU base address
145                    // a locomotive in a Double Header, we
146                    // want to take further action.  If the
147                    // message tells us we've reached the end
148                    // of the stack, then we can quit. Otherwise,
149                    // we just request the next address.
150                    if (log.isDebugEnabled()) {
151                        log.debug("Message Received in SEARCHREQUESTSENT state.  Message is: {}",l);
152                    }
153                    if (l.getElement(0) == XNetConstants.LOCO_INFO_RESPONSE) {
154                        switch (l.getElement(1)) {
155                            case XNetConstants.LOCO_SEARCH_RESPONSE_N:
156                            case XNetConstants.LOCO_SEARCH_RESPONSE_MU:
157                                _lastAddress = l.getThrottleMsgAddr();
158                                searchNext();
159                                break;
160                            case XNetConstants.LOCO_SEARCH_RESPONSE_DH:
161                                _lastAddress = l.getThrottleMsgAddr();
162                                _lastMUAddress = _lastAddress;
163                                _lastMemberAddress = _lastAddress;
164                                if (log.isDebugEnabled()) {
165                                    log.debug("Sending search for first DH Entry information , _lastMemberAddress is: {}", _lastMemberAddress);
166                                }
167                                currentState = DHADDRESS1INFO;
168                                XNetMessage msg = XNetMessage.getLocomotiveInfoRequestMsg(_lastMemberAddress);
169                                tc.sendXNetMessage(msg, this);
170                                break;
171                            case XNetConstants.LOCO_SEARCH_RESPONSE_MU_BASE:
172                                _lastAddress = l.getThrottleMsgAddr();
173                                _lastMUAddress = _lastAddress;
174                                currentConsist = (XNetConsist) addConsist(
175                                        new DccLocoAddress(_lastMUAddress, false));
176                                searchMU();
177                                break;
178                            case XNetConstants.LOCO_SEARCH_NO_RESULT:
179                                currentState = IDLE;
180                                notifyConsistListChanged();
181                                requestingUpdate = false;
182                                break;
183                            case XNetConstants.LOCO_NOT_AVAILABLE:
184                            case XNetConstants.LOCO_FUNCTION_STATUS:
185                            default: // Do Nothing by default
186                        }
187                    }
188                    break;
189                case MUSEARCHSENT:
190                    if (log.isDebugEnabled()) {
191                        log.debug("Message Received in MUSEARCHSENT state.  Message is: {}",l);
192                    }
193                    if (l.getElement(0) == XNetConstants.LOCO_INFO_RESPONSE) {
194                        switch (l.getElement(1)) {
195                            case XNetConstants.LOCO_SEARCH_RESPONSE_MU:
196                                _lastMemberAddress = l.getThrottleMsgAddr();
197                                if (_lastMemberAddress != 0) {
198                                    // Find out the direction information
199                                    // for the address in question.
200                                    requestInfoMU();
201                                } else {
202                                    // We reached the end of this consist,
203                                    // find the next one
204                                    searchNext();
205                                }
206                                break;
207                            case XNetConstants.LOCO_SEARCH_NO_RESULT:
208                                searchNext();
209                                break;
210                            case XNetConstants.LOCO_SEARCH_RESPONSE_DH:
211                            case XNetConstants.LOCO_SEARCH_RESPONSE_MU_BASE:
212                            case XNetConstants.LOCO_SEARCH_RESPONSE_N:
213                            case XNetConstants.LOCO_NOT_AVAILABLE:
214                            case XNetConstants.LOCO_FUNCTION_STATUS:
215                            default: // Do Nothing by default
216                        }
217                    }
218                    break;
219                case MUINFOREQUESTSENT:
220                    if (log.isDebugEnabled()) {
221                        log.debug("Message Received in MUINFOREQUESTSENT state.  Message is: {}",l);
222                    }
223                    if (l.getElement(0) == XNetConstants.LOCO_INFO_MUED_UNIT) {
224                        currentConsist.restore(new DccLocoAddress(_lastMemberAddress, _lastMemberAddress > 99),
225                                (l.getElement(2) & 0x80) == 0x80);
226                        searchMU();
227                    }
228                    break;
229                case DHADDRESS1INFO:
230                    if (log.isDebugEnabled()) {
231                        log.debug("Message Received in DHADDRESS1INFO state.  Message is: {}",l);
232                    }
233                    if (l.getElement(0) == XNetConstants.LOCO_INFO_DH_UNIT) {
234                        DccLocoAddress firstMember = new DccLocoAddress(_lastMemberAddress, _lastMemberAddress > 99);
235                        int AH = l.getElement(5);
236                        int AL = l.getElement(6);
237                        if (AH == 0x00) {
238                            _lastMemberAddress = AL;
239                        } else {
240                            _lastMemberAddress = ((AH * 256) & 0xFF00)
241                                    + (AL & 0xFF)
242                                    - 0xC000;
243                        }
244
245                        // We need to check and see if this consist exists
246                        if (!XNetConsistManager.this.consistTable.containsKey(firstMember)
247                                && !XNetConsistManager.this.consistTable.containsKey(new DccLocoAddress(_lastMemberAddress, _lastMemberAddress > 99))) {
248                            currentConsist = (XNetConsist) addConsist(firstMember);
249                            currentConsist.setConsistType(Consist.CS_CONSIST);
250                            currentConsist.restore(firstMember,
251                                    (l.getElement(2) & 0x80) == 0x80);
252                            if (log.isDebugEnabled()) {
253                                log.debug("Sending search for second DH Entry information , _lastMemberAddress is: {}", _lastMemberAddress);
254                            }
255                            currentState = DHADDRESS2INFO;
256                            XNetMessage msg = XNetMessage
257                                    .getLocomotiveInfoRequestMsg(
258                                            _lastMemberAddress);
259                            tc.sendXNetMessage(msg, this);
260                        } else {
261                            // This consist already exists
262                            searchNext();
263                        }
264                    }
265                    break;
266                case DHADDRESS2INFO:
267                    log.debug("Message Received in DHADDRESS2INFO state.  Message is: {}",l);
268                    if (l.getElement(0) == XNetConstants.LOCO_INFO_DH_UNIT) {
269                        currentConsist.restore(new DccLocoAddress(_lastMemberAddress, _lastMemberAddress > 99),
270                                (l.getElement(2) & 0x80) == 0x80);
271                    }
272                    // We reached the end of this consist,
273                    // find the next one
274                    searchNext();
275                    break;
276                case IDLE:
277                default:
278                    log.debug("Message Received in default(IDLE) state. Message is: {}", l);
279            }
280        }
281
282        /**
283         * Listener for messages to the command station.
284         */
285        @Override
286        public void message(XNetMessage l) {
287            // this class does not utilize outgoing messages
288        }
289
290        /**
291         * Handle a timeout notification.
292         */
293        @Override
294        public void notifyTimeout(XNetMessage msg) {
295            if (log.isDebugEnabled()) {
296                log.debug("Notified of timeout on message {}", msg);
297            }
298        }
299    }
300
301    private static final Logger log = LoggerFactory.getLogger(XNetConsistManager.class);
302}