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        notifyConsistListChanged();
066        return (consist);
067    }
068
069    /**
070     * Request an update from the layout, loading Consists from the command
071     * station.
072     */
073    @Override
074    public void requestUpdateFromLayout() {
075        if (shouldRequestUpdateFromLayout()) {
076            // Initilize the consist reader.
077            new XNetConsistReader();
078        }
079    }
080
081    @Override
082    protected boolean shouldRequestUpdateFromLayout() {
083        return !requestingUpdate;
084    }
085
086    /**
087     * Internal class to read consists from the Command Station.
088     */
089    private class XNetConsistReader implements XNetListener {
090
091        // Storage for addresses
092        int _lastMUAddress = 0;
093        int _lastAddress = 0;
094        int _lastMemberAddress = 0;
095        XNetConsist currentConsist = null;
096        // Possible States
097        static final int IDLE = 0;
098        static final int SEARCHREQUESTSENT = 1;
099        static final int MUSEARCHSENT = 2;
100        static final int MUINFOREQUESTSENT = 4;
101        static final int DHADDRESS1INFO = 8;
102        static final int DHADDRESS2INFO = 16;
103        // Current State
104        int currentState = IDLE;
105
106        XNetConsistReader() {
107            // Register as an XPressNet Listener
108            tc.addXNetListener(XNetInterface.COMMINFO
109                    | XNetInterface.THROTTLE
110                    | XNetInterface.CONSIST,
111                    this);
112            searchNext();
113        }
114
115        private void searchNext() {
116            requestingUpdate = true;
117            log.debug("Sending search for next DB Entry, _lastAddress is: {}",_lastAddress);
118            currentState = SEARCHREQUESTSENT;
119            XNetMessage msg = XNetMessage.getNextAddressOnStackMsg(_lastAddress, true);
120            tc.sendXNetMessage(msg, this);
121        }
122
123        private void searchMU() {
124            log.debug("Sending search for next MU Entry, _lastMUAddress is: {} _lastMemberAddress is: {}",_lastMUAddress,_lastMemberAddress);
125            currentState = MUSEARCHSENT;
126            XNetMessage msg = XNetMessage.getDBSearchMsgNextMULoco(_lastMUAddress, _lastMemberAddress, true);
127            tc.sendXNetMessage(msg, this);
128        }
129
130        private void requestInfoMU() {
131            log.debug("Sending search for next MU Entry information , _lastMemberAddress is: {}",_lastMemberAddress);
132            currentState = MUINFOREQUESTSENT;
133            XNetMessage msg = XNetMessage.getLocomotiveInfoRequestMsg(_lastMemberAddress);
134            tc.sendXNetMessage(msg, this);
135        }
136
137        // Listener for messages from the command station
138        @Override
139        public void message(XNetReply l) {
140            switch (currentState) {
141                case SEARCHREQUESTSENT:
142                    // We sent a request to search the stack.
143                    // We need to find out what type of message
144                    // was recived as a response.  If We're
145                    // told the message is for an MU base address
146                    // a locomotive in a Double Header, we
147                    // want to take further action.  If the
148                    // message tells us we've reached the end
149                    // of the stack, then we can quit. Otherwise,
150                    // we just request the next address.
151                    if (log.isDebugEnabled()) {
152                        log.debug("Message Received in SEARCHREQUESTSENT state.  Message is: {}",l);
153                    }
154                    if (l.getElement(0) == XNetConstants.LOCO_INFO_RESPONSE) {
155                        switch (l.getElement(1)) {
156                            case XNetConstants.LOCO_SEARCH_RESPONSE_N:
157                            case XNetConstants.LOCO_SEARCH_RESPONSE_MU:
158                                _lastAddress = l.getThrottleMsgAddr();
159                                searchNext();
160                                break;
161                            case XNetConstants.LOCO_SEARCH_RESPONSE_DH:
162                                _lastAddress = l.getThrottleMsgAddr();
163                                _lastMUAddress = _lastAddress;
164                                _lastMemberAddress = _lastAddress;
165                                if (log.isDebugEnabled()) {
166                                    log.debug("Sending search for first DH Entry information , _lastMemberAddress is: {}", _lastMemberAddress);
167                                }
168                                currentState = DHADDRESS1INFO;
169                                XNetMessage msg = XNetMessage.getLocomotiveInfoRequestMsg(_lastMemberAddress);
170                                tc.sendXNetMessage(msg, this);
171                                break;
172                            case XNetConstants.LOCO_SEARCH_RESPONSE_MU_BASE:
173                                _lastAddress = l.getThrottleMsgAddr();
174                                _lastMUAddress = _lastAddress;
175                                currentConsist = (XNetConsist) addConsist(
176                                        new DccLocoAddress(_lastMUAddress, false));
177                                searchMU();
178                                break;
179                            case XNetConstants.LOCO_SEARCH_NO_RESULT:
180                                currentState = IDLE;
181                                notifyConsistListChanged();
182                                requestingUpdate = false;
183                                break;
184                            case XNetConstants.LOCO_NOT_AVAILABLE:
185                            case XNetConstants.LOCO_FUNCTION_STATUS:
186                            default: // Do Nothing by default
187                        }
188                    }
189                    break;
190                case MUSEARCHSENT:
191                    if (log.isDebugEnabled()) {
192                        log.debug("Message Received in MUSEARCHSENT state.  Message is: {}",l);
193                    }
194                    if (l.getElement(0) == XNetConstants.LOCO_INFO_RESPONSE) {
195                        switch (l.getElement(1)) {
196                            case XNetConstants.LOCO_SEARCH_RESPONSE_MU:
197                                _lastMemberAddress = l.getThrottleMsgAddr();
198                                if (_lastMemberAddress != 0) {
199                                    // Find out the direction information
200                                    // for the address in question.
201                                    requestInfoMU();
202                                } else {
203                                    // We reached the end of this consist,
204                                    // find the next one
205                                    searchNext();
206                                }
207                                break;
208                            case XNetConstants.LOCO_SEARCH_NO_RESULT:
209                                searchNext();
210                                break;
211                            case XNetConstants.LOCO_SEARCH_RESPONSE_DH:
212                            case XNetConstants.LOCO_SEARCH_RESPONSE_MU_BASE:
213                            case XNetConstants.LOCO_SEARCH_RESPONSE_N:
214                            case XNetConstants.LOCO_NOT_AVAILABLE:
215                            case XNetConstants.LOCO_FUNCTION_STATUS:
216                            default: // Do Nothing by default
217                        }
218                    }
219                    break;
220                case MUINFOREQUESTSENT:
221                    if (log.isDebugEnabled()) {
222                        log.debug("Message Received in MUINFOREQUESTSENT state.  Message is: {}",l);
223                    }
224                    if (l.getElement(0) == XNetConstants.LOCO_INFO_MUED_UNIT) {
225                        currentConsist.restore(new DccLocoAddress(_lastMemberAddress, _lastMemberAddress > 99),
226                                (l.getElement(2) & 0x80) == 0x80);
227                        searchMU();
228                    }
229                    break;
230                case DHADDRESS1INFO:
231                    if (log.isDebugEnabled()) {
232                        log.debug("Message Received in DHADDRESS1INFO state.  Message is: {}",l);
233                    }
234                    if (l.getElement(0) == XNetConstants.LOCO_INFO_DH_UNIT) {
235                        DccLocoAddress firstMember = new DccLocoAddress(_lastMemberAddress, _lastMemberAddress > 99);
236                        int AH = l.getElement(5);
237                        int AL = l.getElement(6);
238                        if (AH == 0x00) {
239                            _lastMemberAddress = AL;
240                        } else {
241                            _lastMemberAddress = ((AH * 256) & 0xFF00)
242                                    + (AL & 0xFF)
243                                    - 0xC000;
244                        }
245
246                        // We need to check and see if this consist exists
247                        if (!XNetConsistManager.this.consistTable.containsKey(firstMember)
248                                && !XNetConsistManager.this.consistTable.containsKey(new DccLocoAddress(_lastMemberAddress, _lastMemberAddress > 99))) {
249                            currentConsist = (XNetConsist) addConsist(firstMember);
250                            currentConsist.setConsistType(Consist.CS_CONSIST);
251                            currentConsist.restore(firstMember,
252                                    (l.getElement(2) & 0x80) == 0x80);
253                            if (log.isDebugEnabled()) {
254                                log.debug("Sending search for second DH Entry information , _lastMemberAddress is: {}", _lastMemberAddress);
255                            }
256                            currentState = DHADDRESS2INFO;
257                            XNetMessage msg = XNetMessage
258                                    .getLocomotiveInfoRequestMsg(
259                                            _lastMemberAddress);
260                            tc.sendXNetMessage(msg, this);
261                        } else {
262                            // This consist already exists
263                            searchNext();
264                        }
265                    }
266                    break;
267                case DHADDRESS2INFO:
268                    log.debug("Message Received in DHADDRESS2INFO state.  Message is: {}",l);
269                    if (l.getElement(0) == XNetConstants.LOCO_INFO_DH_UNIT) {
270                        currentConsist.restore(new DccLocoAddress(_lastMemberAddress, _lastMemberAddress > 99),
271                                (l.getElement(2) & 0x80) == 0x80);
272                    }
273                    // We reached the end of this consist,
274                    // find the next one
275                    searchNext();
276                    break;
277                case IDLE:
278                default:
279                    log.debug("Message Received in default(IDLE) state. Message is: {}", l);
280            }
281        }
282
283        /**
284         * Listener for messages to the command station.
285         */
286        @Override
287        public void message(XNetMessage l) {
288            // this class does not utilize outgoing messages
289        }
290
291        /**
292         * Handle a timeout notification.
293         */
294        @Override
295        public void notifyTimeout(XNetMessage msg) {
296            if (log.isDebugEnabled()) {
297                log.debug("Notified of timeout on message {}", msg);
298            }
299        }
300    }
301
302    private static final Logger log = LoggerFactory.getLogger(XNetConsistManager.class);
303}