001package jmri.jmrix.dccpp; 002 003import static jmri.jmrix.dccpp.DCCppConstants.MAX_TURNOUT_ADDRESS; 004 005import java.util.ArrayDeque; 006import java.util.Locale; 007import java.util.Queue; 008 009import javax.annotation.Nonnull; 010import jmri.Turnout; 011import org.slf4j.Logger; 012import org.slf4j.LoggerFactory; 013 014/** 015 * Implement TurnoutManager for DCC-EX systems. 016 * <p> 017 * System names are "DxppTnnn", where Dx is the system prefix and nnn is the turnout number without padding. 018 * 019 * @author Bob Jacobsen Copyright (C) 2001 020 * @author Paul Bender Copyright (C) 2003-2010 021 * @author Mark Underwood Copyright (C) 2015 022 */ 023public class DCCppTurnoutManager extends jmri.managers.AbstractTurnoutManager implements DCCppListener { 024 025 protected DCCppTrafficController tc = null; 026 private final Queue<Integer> _pendingTurnoutIds = new ArrayDeque<>(); 027 028 /** 029 * Create a new DCC-EX TurnoutManager. 030 * Has to register for DCC-EX events. 031 * 032 * @param memo the supporting system connection memo 033 */ 034 public DCCppTurnoutManager(DCCppSystemConnectionMemo memo) { 035 super(memo); 036 tc = memo.getDCCppTrafficController(); 037 // set up listener 038 tc.addDCCppListener(DCCppInterface.FEEDBACK, this); 039 // request list of turnouts 040 tc.sendDCCppMessage(DCCppMessage.makeTurnoutListMsg(), this); 041 // request list of outputs 042 tc.sendDCCppMessage(DCCppMessage.makeOutputListMsg(), this); 043 // request list of Turnout IDs 044 tc.sendDCCppMessage(DCCppMessage.makeTurnoutIDsMsg(), this); 045 } 046 047 /** 048 * {@inheritDoc} 049 */ 050 @Override 051 @Nonnull 052 public DCCppSystemConnectionMemo getMemo() { 053 return (DCCppSystemConnectionMemo) memo; 054 } 055 056 // DCCpp-specific methods 057 058 /** 059 * {@inheritDoc} 060 */ 061 @Nonnull 062 @Override 063 protected Turnout createNewTurnout(@Nonnull String systemName, String userName) throws IllegalArgumentException { 064 // check if the output bit is available 065 int bitNum = getBitFromSystemName(systemName); 066 if (bitNum < 0) { 067 throw new IllegalArgumentException("Cannot get Bit from System Name " + systemName); 068 } 069 // make the new Turnout object 070 Turnout t = new DCCppTurnout(getSystemPrefix(), bitNum, tc); 071 t.setUserName(userName); 072 return t; 073 } 074 075 /** 076 * {@inheritDoc} 077 * Listen for turnouts, creating them as needed. 078 */ 079 @Override 080 public void message(DCCppReply l) { 081 if (l.isTurnoutReply()) { 082 log.debug("received Turnout Reply message: '{}'", l); 083 // parse message type 084 int addr = l.getTOIDInt(); 085 if (addr >= 0) { 086 // check to see if the address has been operated before 087 // continuing. 088 log.debug("message has address: {}", addr); 089 // reach here for switch command; make sure we know 090 // about this one 091 String s = getSystemNamePrefix() + addr; 092 DCCppTurnout found = (DCCppTurnout) getBySystemName(s); 093 if ( found == null) { 094 // need to create a new one, set some attributes, 095 // and send the message on to the newly created object. 096 DCCppTurnout t = (DCCppTurnout) provideTurnout(s); 097 t.setFeedbackMode(Turnout.MONITORING); 098 t.initmessage(l); 099 } else { 100 // The turnout exists, forward this message to the 101 // turnout 102 found.message(l); 103 } 104 } 105 } else if (l.isOutputReply()) { 106 log.debug("received Output Reply message: '{}'", l); 107 // parse message type 108 int addr = l.getOutputNumInt(); 109 if (addr >= 0) { 110 // check to see if the address has been operated before 111 // continuing. 112 log.debug("message has address: {}", addr); 113 // reach here for switch command; make sure we know 114 // about this one 115 String s = getSystemNamePrefix() + addr; 116 DCCppTurnout found = (DCCppTurnout) getBySystemName(s); 117 if (found == null) { 118 // need to create a new one, and send the message on 119 // to the newly created object. 120 DCCppTurnout t = (DCCppTurnout) provideTurnout(s); 121 t.setFeedbackMode(Turnout.EXACT); 122 t.initmessage(l); 123 } else { 124 // The turnout exists, forward this message to the 125 // turnout 126 found.message(l); 127 } 128 } 129 } else if (l.isTurnoutIDsReply()) { 130 log.debug("received Turnout ID List message: '{}'", l); 131 for (Integer id : l.getTurnoutIDList()) { 132 if (!_pendingTurnoutIds.contains(id)) { 133 _pendingTurnoutIds.add(id); 134 } 135 } 136 requestNextTurnoutDetail(); 137 } else if (l.isTurnoutIDReply()) { 138 log.debug("received Turnout ID Detail message: '{}'", l); 139 // parse message type 140 int addr = l.getTOIDInt(); 141 if (addr >= 0) { 142 log.debug("message has address: {}", addr); 143 String s = getSystemNamePrefix() + addr; 144 DCCppTurnout found = (DCCppTurnout) getBySystemName(s); 145 if ( found == null) { //create new turnout 146 DCCppTurnout t = (DCCppTurnout) provideTurnout(s); 147 t.setFeedbackMode(Turnout.MONITORING); 148 if (!l.getTurnoutDescString().isEmpty()) { 149 t.setUserName(l.getTurnoutDescString()); //add username if available 150 } 151 t.initmessage(l); //forward message to turnout 152 } else { // The turnout already exists 153 if (!l.getTurnoutDescString().isEmpty() && found.getUserName()==null) { 154 found.setUserName(l.getTurnoutDescString()); //set userName if needed and available 155 } 156 found.message(l); //forward message to turnout 157 } 158 } 159 requestNextTurnoutDetail(); 160 } 161 } 162 163 private void requestNextTurnoutDetail() { 164 Integer id = _pendingTurnoutIds.poll(); 165 if (id != null) { 166 tc.sendDCCppMessage(DCCppMessage.makeTurnoutIDMsg(id), this); 167 } 168 } 169 170 /** 171 * Get text to be used for the Turnout.CLOSED state in user communication. 172 * Allows text other than "CLOSED" to be use with certain hardware system to 173 * represent the Turnout.CLOSED state. 174 */ 175 @Override 176 @Nonnull 177 public String getClosedText() { 178 return Bundle.getMessage("TurnoutStateClosed"); 179 } 180 181 /** 182 * Get text to be used for the Turnout.THROWN state in user communication. 183 * Allows text other than "THROWN" to be use with certain hardware system to 184 * represent the Turnout.THROWN state. 185 */ 186 @Override 187 @Nonnull 188 public String getThrownText() { 189 return Bundle.getMessage("TurnoutStateThrown"); 190 } 191 192 /** 193 * Listen for the outgoing messages (to the command station) 194 */ 195 @Override 196 public void message(DCCppMessage l) { 197 } 198 199 // Handle message timeout notification 200 // If the message still has retries available, reduce retries and send it back to the traffic controller. 201 @Override 202 public void notifyTimeout(DCCppMessage msg) { 203 log.debug("Notified of timeout on message '{}' , {} retries available.", msg, msg.getRetries()); 204 if (msg.getRetries() > 0) { 205 msg.setRetries(msg.getRetries() - 1); 206 tc.sendDCCppMessage(msg, this); 207 } 208 } 209 210 /** {@inheritDoc} */ 211 @Override 212 public boolean allowMultipleAdditions(@Nonnull String systemName) { 213 return true; 214 } 215 216 /** 217 * {@inheritDoc} 218 */ 219 @Override 220 public NameValidity validSystemNameFormat(@Nonnull String systemName) { 221 return (getBitFromSystemName(systemName) != -1) ? NameValidity.VALID : NameValidity.INVALID; 222 } 223 224 /** 225 * {@inheritDoc} 226 */ 227 @Override 228 @Nonnull 229 public String validateSystemNameFormat(@Nonnull String systemName, @Nonnull Locale locale) { 230 return validateIntegerSystemNameFormat(systemName, 0, MAX_TURNOUT_ADDRESS, locale); 231 } 232 233 /** 234 * Get the bit address from the system name. 235 * 236 * @param systemName a valid Turnout System Name 237 * @return the turnout number extracted from the system name 238 */ 239 public int getBitFromSystemName(String systemName) { 240 try { 241 validateSystemNameFormat(systemName, Locale.getDefault()); 242 } catch (IllegalArgumentException ex) { 243 return -1; 244 } 245 return Integer.parseInt(systemName.substring(getSystemNamePrefix().length())); 246 } 247 248 /** {@inheritDoc} */ 249 @Override 250 public String getEntryToolTip() { 251 return Bundle.getMessage("AddOutputEntryToolTip"); 252 } 253 254 private static final Logger log = LoggerFactory.getLogger(DCCppTurnoutManager.class); 255 256}