001/**
002 * Consist Manager for use with the LocoNetConsist class for the
003 * consists it builds.
004 *
005 * @author Paul Bender Copyright (C) 2011
006 */
007package jmri.jmrix.loconet;
008
009import jmri.Consist;
010import jmri.LocoAddress;
011import jmri.DccLocoAddress;
012import jmri.implementation.AbstractConsistManager;
013import jmri.jmrix.loconet.SlotMapEntry.SlotType;
014
015import org.slf4j.Logger;
016import org.slf4j.LoggerFactory;
017
018public class LocoNetConsistManager extends AbstractConsistManager {
019
020    private LocoNetSystemConnectionMemo memo = null;
021    private boolean requestingUpdate = false;
022
023    /**
024     * Constructor - call the constructor for the superclass, and initialize the
025     * consist reader thread, which retrieves consist information from the
026     * command station
027     *
028     * @param lm the LocoNetSystemConnectionMemo to which this object is related
029     */
030    public LocoNetConsistManager(LocoNetSystemConnectionMemo lm) {
031        super();
032        this.memo = lm;
033    }
034
035    /**
036     * This implementation does support command station assisted consists, so
037     * return true.
038     *
039     */
040    @Override
041    public boolean isCommandStationConsistPossible() {
042        return true;
043    }
044
045    /**
046     * Does a CS consist require a separate consist address?
047     *
048     */
049    @Override
050    public boolean csConsistNeedsSeperateAddress() {
051        return false;
052    }
053
054    /**
055     * Add a new LocoNetConsist with the given address to
056     * consistTable/consistList
057     */
058    @Override
059    public Consist addConsist(LocoAddress address) {
060        if (! (address instanceof DccLocoAddress)) {
061            throw new IllegalArgumentException("address is not a DccLocoAddress object");
062        }
063        if (consistTable.containsKey(address)) // no duplicates allowed.
064        {
065            return consistTable.get(address);
066        }
067        LocoNetConsist consist;
068        consist = new LocoNetConsist((DccLocoAddress) address, memo);
069        consistTable.put(address, consist);
070        notifyConsistListChanged();
071        return consist;
072    }
073
074    /* request an update from the layout, loading
075     * Consists from the command station.
076     *
077     * On a LocoNet command station, the consists are stored in the
078     * slots in an array based tree.  Each node in a consist contains
079     * a pointer to the "top" slot in the consist.  A top slot is
080     * allowed to be a member of another consist.  When this occurs,
081     * it is labeled as a "mid" locomotive.
082     *
083     * This function updates the list of consists by scanning the
084     * slots and adding new "top" slot addresses and removing address
085     * that are no longer "top" locomotives.
086     */
087    @Override
088    public void requestUpdateFromLayout() {
089        if (!shouldRequestUpdateFromLayout()) {
090            return;
091        }
092        requestingUpdate = true;
093        SlotManager sm = memo.getSlotManager();
094
095        // in the first pass, check for consists top addresses in the
096        // command station slots.
097        for (int i = 0; i < sm.getNumSlots(); i++) {
098            if (sm.slot(i).getSlotType() == SlotType.LOCO) {
099                LocoNetSlot s = sm.slot(i);
100                DccLocoAddress address = new DccLocoAddress(s.locoAddr(), LnThrottleManager.isLongAddress(s.locoAddr()));
101                if (log.isDebugEnabled()) {
102                    log.debug(" Slot {} Address {} consist status {}", i, address, LnConstants.CONSIST_STAT(s.consistStatus()));
103                }
104                // Only CONSIST_TOP is treated as a consist top here.
105                // CONSIST_MID means a locomotive that has a lead above it
106                // AND another member pointing to it below -- normal for
107                // any chain of 3+ members. It is a MEMBER, never its own
108                // top -- the second pass below handles CONSIST_MID by
109                // following its pointer to its real lead.
110                if (s.consistStatus() == LnConstants.CONSIST_TOP) {
111                    // this is a consist top, add it to the list, if it is not there
112                    // already.
113                    //
114                    // Also skip if this address is already tracked as a
115                    // MEMBER of some other consist -- a locomotive mid-way
116                    // through being linked into a consist can briefly still
117                    // report CONSIST_TOP on the wire before it's linked, so
118                    // this avoids registering a phantom standalone consist
119                    // for an address already claimed elsewhere.
120                    if (!consistTable.containsKey(address) && !isAlreadyConsistMember(address)) {
121                        if (log.isDebugEnabled()) {
122                            log.debug("Adding Consist with Address {} due to command station read", address);
123                        }
124                        addConsist(address);
125                        getConsist(address).add(address, true); // add the address to the consist.
126                    }
127                }
128            }
129        }
130
131        // make a second pass, this time looking for locomotives in a consist.
132        for (int i = 0; i < sm.getNumSlots(); i++) {
133            if (sm.slot(i).getSlotType() == SlotType.LOCO) {
134                LocoNetSlot s = sm.slot(i);
135                DccLocoAddress address = new DccLocoAddress(s.locoAddr(), LnThrottleManager.isLongAddress(s.locoAddr()));
136                if (log.isDebugEnabled()) {
137                    log.debug(" Slot {} Address {} consist status {}", i, address, LnConstants.CONSIST_STAT(s.consistStatus()));
138                }
139                if (s.consistStatus() == LnConstants.CONSIST_SUB || s.consistStatus() == LnConstants.CONSIST_MID) {
140                    // this is a consist member, add it to the consist in the
141                    // slot which it has a pointer to (the slot pointer is stored in
142                    // the slot's speed).
143                    //
144                    // Verifies the pointed-to slot is ACTUALLY a live top/mid
145                    // right now before trusting it as this member's real
146                    // lead -- a slot's "speed" pointer can still reference a
147                    // slot number that used to be some earlier, now-defunct
148                    // consist's lead, since the pointer itself doesn't get
149                    // cleared just because that old lead relationship ended.
150                    LocoNetSlot leadSlot = sm.slot(s.speed());
151                    if (leadSlot.consistStatus() == LnConstants.CONSIST_TOP || leadSlot.consistStatus() == LnConstants.CONSIST_MID) {
152                        DccLocoAddress lead = new DccLocoAddress(leadSlot.locoAddr(), LnThrottleManager.isLongAddress(leadSlot.locoAddr()));
153                        getConsist(lead).add(address, s.isForward() == leadSlot.isForward());
154                    } else if (log.isDebugEnabled()) {
155                        log.debug("Slot {} (address {}) claims consist member status but its lead pointer (slot {}) isn't currently a top/mid -- stale pointer, ignoring",
156                                i, address, s.speed());
157                    }
158                }
159            }
160        }
161        requestingUpdate = false;
162    }
163
164    @Override
165    protected boolean shouldRequestUpdateFromLayout() {
166        return !requestingUpdate;
167    }
168
169    /**
170     * Is address already tracked as a MEMBER of some other already-known
171     * consist? See the field report on the CONSIST_TOP check above --
172     * used to avoid registering a phantom standalone consist for an
173     * address that's really just mid-link into an existing one.
174     */
175    private boolean isAlreadyConsistMember(DccLocoAddress address) {
176        for (Consist c : consistTable.values()) {
177            if (!address.equals(c.getConsistAddress()) && c.contains(address)) {
178                return true;
179            }
180        }
181        return false;
182    }
183
184    private static final Logger log = LoggerFactory.getLogger(LocoNetConsistManager.class);
185}