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}