001/**
002 * Consist Manager for use with the EasyDccConsist class for the
003 * consists it builds.
004 *
005 * @author Paul Bender Copyright (C) 2006
006 */
007package jmri.jmrix.easydcc;
008
009import jmri.Consist;
010import jmri.LocoAddress;
011import jmri.DccLocoAddress;
012import jmri.implementation.AbstractConsistManager;
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
015
016public class EasyDccConsistManager extends AbstractConsistManager {
017
018    private EasyDccConsistReader reader;
019    private EasyDccSystemConnectionMemo _memo = null;
020    private EasyDccTrafficController trafficController = null;
021
022    /**
023     * Constructor - call the constructor for the superclass, and initialize the
024     * consist reader thread, which retrieves consist information from the
025     * command station.
026     *
027     * @param memo the associated connection memo
028     */
029    public EasyDccConsistManager(EasyDccSystemConnectionMemo memo) {
030        super();
031        reader = new EasyDccConsistReader();
032        _memo = memo;
033        // connect to the TrafficManager
034        trafficController = memo.getTrafficController();
035    }
036
037    /**
038     * This implementation does support advanced consists, so return true.
039     *
040     */
041    @Override
042    public boolean isCommandStationConsistPossible() {
043        return true;
044    }
045
046    /**
047     * Does a CS consist require a separate consist address? CS consist
048     * addresses are assigned by the user, so return true.
049     *
050     */
051    @Override
052    public boolean csConsistNeedsSeperateAddress() {
053        return true;
054    }
055
056    /**
057     * Add a new EasyDccConsist with the given address to
058     * consistTable/consistList.
059     */
060    @Override
061    public Consist addConsist(LocoAddress address) {
062        if (! (address instanceof DccLocoAddress)) {
063            throw new IllegalArgumentException("address is not a DccLocoAddress object");
064        }
065        if (consistTable.containsKey(address)) { // no duplicates allowed across all connections
066            return consistTable.get(address);
067        }
068        EasyDccConsist consist;
069        consist = new EasyDccConsist((DccLocoAddress) address, _memo);
070        consistTable.put(address, consist);
071        notifyConsistListChanged();
072        return consist;
073    }
074
075    /* Request an update from the layout, loading
076     * Consists from the command station.
077     */
078    @Override
079    public void requestUpdateFromLayout() {
080        if (this.shouldRequestUpdateFromLayout()) {
081            reader.searchNext();
082        }
083    }
084
085    @Override
086    protected boolean shouldRequestUpdateFromLayout() {
087        return (reader.currentState == EasyDccConsistReader.IDLE);
088    }
089
090    /**
091     * Internal class to read consists from the command station.
092     */
093    private class EasyDccConsistReader implements Runnable, EasyDccListener {
094
095        // Storage for addresses
096        int _lastAddress = 0;
097        // Possible States
098        final static int IDLE = 0;
099        final static int SEARCHREQUESTSENT = 1;
100        // Current State
101        int currentState = IDLE;
102
103        EasyDccConsistReader() {
104        }
105
106        @Override
107        public void run() {
108        }
109
110        private void searchNext() {
111            log.debug("Sending request for next consist, _lastAddress is: {}", _lastAddress);
112            currentState = SEARCHREQUESTSENT;
113            EasyDccMessage msg = EasyDccMessage.getDisplayConsist(++_lastAddress);
114            trafficController.sendEasyDccMessage(msg, this);
115        }
116
117        // Listener for messages from the command station
118        @Override
119        public void reply(EasyDccReply r) {
120            if (currentState == SEARCHREQUESTSENT) {
121                // We sent a request for a consist address.
122                // We need to find out what type of message 
123                // was received as a response.  If the message
124                // has an opcode of 'G', then it is a response 
125                // to the Display Consist instruction we sent 
126                // previously.  If the message has any other
127                // opcode, we can ignore the message.
128                if (log.isDebugEnabled()) {
129                    log.debug("Message Received in SEARCHREQUESTSENT state. Message is: {}", r.toString());
130                }
131                if (r.getOpCode() == 'G') {
132                    // This is the response we're looking for
133                    // The bytes 2 and 3 are the ...
134
135                    int consistAddr;
136                    Boolean newConsist = true;
137                    EasyDccConsist currentConsist = null;
138                    String sa = "" + (char) r.getElement(1)
139                            + (char) r.getElement(2);
140                    consistAddr = Integer.valueOf(sa, 16).intValue();
141
142                    // The rest of the message consists of 4 hex digits
143                    // for each of up to 8 locomotives.
144                    for (int i = 3; i < r.getNumDataElements(); i += 4) {
145                        DccLocoAddress locoAddress;
146                        int tempAddr;
147                        boolean directionNormal;
148                        if ((char) r.getElement(i) == ' ') {
149                            i++; // skip a space
150                        } else if (java.lang.Character.digit((char) r.getElement(i), 16) == -1) {
151                            break; // stop the loop if we don't have a hex digit.
152                        }
153                        String sb = "" + (char) r.getElement(i)
154                                + (char) r.getElement(i + 1)
155                                + (char) r.getElement(i + 2)
156                                + (char) r.getElement(i + 3);
157                        tempAddr = Integer.valueOf(sb, 16).intValue();
158                        directionNormal = ((tempAddr & 0x8000) == 0);
159                        if (tempAddr != 0) {
160                            if (newConsist) {
161                                // This is the first address, add the
162                                // consist
163                                currentConsist = (EasyDccConsist) addConsist(
164                                        new DccLocoAddress(consistAddr, false));
165                                newConsist = false;
166                            }
167                            locoAddress = new DccLocoAddress(
168                                    tempAddr & 0x7fff, (tempAddr & 0x7fff) > 99);
169                            if (currentConsist != null) {
170                                currentConsist.restore(locoAddress, directionNormal);
171                            } else //should never happen since currentConsist gets set in the first pass
172                            {
173                                log.error("currentConsist is null!");
174                            }
175                        }
176                    }
177                    if (_lastAddress < 255) {
178                        searchNext();
179                    } else {
180                        currentState = IDLE;
181                        notifyConsistListChanged();
182                    }
183                } else {
184                    if (log.isDebugEnabled()) {
185                        log.debug("Message Received in IDLE state. Message is: {}", r.toString());
186                    }
187                }
188            }
189        }
190
191        // Listener for messages to the command station
192        @Override
193        public void message(EasyDccMessage m) {
194        }
195    }
196
197    private final static Logger log = LoggerFactory.getLogger(EasyDccConsistManager.class);
198
199}