001package jmri.jmrix.dccpp; 002 003import static jmri.jmrix.dccpp.DCCppConstants.MAX_TURNOUT_ADDRESS; 004 005import java.util.Locale; 006import java.util.ArrayList; 007 008import javax.annotation.Nonnull; 009import jmri.Turnout; 010import org.slf4j.Logger; 011import org.slf4j.LoggerFactory; 012 013/** 014 * Implement TurnoutManager for DCC++ systems. 015 * <p> 016 * System names are "DxppTnnn", where Dx is the system prefix and nnn is the turnout number without padding. 017 * 018 * @author Bob Jacobsen Copyright (C) 2001 019 * @author Paul Bender Copyright (C) 2003-2010 020 * @author Mark Underwood Copyright (C) 2015 021 */ 022public class DCCppTurnoutManager extends jmri.managers.AbstractTurnoutManager implements DCCppListener { 023 024 protected DCCppTrafficController tc = null; 025 026 /** 027 * Create a new DCC++ TurnoutManager. 028 * Has to register for DCC++ events. 029 * 030 * @param memo the supporting system connection memo 031 */ 032 public DCCppTurnoutManager(DCCppSystemConnectionMemo memo) { 033 super(memo); 034 tc = memo.getDCCppTrafficController(); 035 // set up listener 036 tc.addDCCppListener(DCCppInterface.FEEDBACK, this); 037 // request list of turnouts 038 tc.sendDCCppMessage(DCCppMessage.makeTurnoutListMsg(), this); 039 // request list of outputs 040 tc.sendDCCppMessage(DCCppMessage.makeOutputListMsg(), this); 041 // request list of Turnout IDs if needed 042 if (tc.getCommandStation().isTurnoutIDsMessageRequired()) { 043 tc.sendDCCppMessage(DCCppMessage.makeTurnoutIDsMsg(), this); 044 } 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 ArrayList<Integer> ids = l.getTurnoutIDList(); 132 for (Integer id : ids) { //request details for each id included in this message 133 tc.sendDCCppMessage(DCCppMessage.makeTurnoutIDMsg(id), this); 134 tc.sendDCCppMessage(DCCppMessage.makeTurnoutImplMsg(id), this); 135 } 136 } else if (l.isTurnoutIDReply()) { 137 log.debug("received Turnout ID Detail message: '{}'", l); 138 // parse message type 139 int addr = l.getTOIDInt(); 140 if (addr >= 0) { 141 log.debug("message has address: {}", addr); 142 String s = getSystemNamePrefix() + addr; 143 DCCppTurnout found = (DCCppTurnout) getBySystemName(s); 144 if ( found == null) { //create new turnout 145 DCCppTurnout t = (DCCppTurnout) provideTurnout(s); 146 t.setFeedbackMode(Turnout.MONITORING); 147 if (!l.getTurnoutDescString().isEmpty()) { 148 t.setUserName(l.getTurnoutDescString()); //add username if available 149 } 150 t.initmessage(l); //forward message to turnout 151 } else { // The turnout already exists 152 if (!l.getTurnoutDescString().isEmpty() && found.getUserName()==null) { 153 found.setUserName(l.getTurnoutDescString()); //set userName if needed and available 154 } 155 found.message(l); //forward message to turnout 156 } 157 } 158 } 159 } 160 161 /** 162 * Get text to be used for the Turnout.CLOSED state in user communication. 163 * Allows text other than "CLOSED" to be use with certain hardware system to 164 * represent the Turnout.CLOSED state. 165 */ 166 @Override 167 @Nonnull 168 public String getClosedText() { 169 return Bundle.getMessage("TurnoutStateClosed"); 170 } 171 172 /** 173 * Get text to be used for the Turnout.THROWN state in user communication. 174 * Allows text other than "THROWN" to be use with certain hardware system to 175 * represent the Turnout.THROWN state. 176 */ 177 @Override 178 @Nonnull 179 public String getThrownText() { 180 return Bundle.getMessage("TurnoutStateThrown"); 181 } 182 183 /** 184 * Listen for the outgoing messages (to the command station) 185 */ 186 @Override 187 public void message(DCCppMessage l) { 188 } 189 190 // Handle message timeout notification 191 // If the message still has retries available, reduce retries and send it back to the traffic controller. 192 @Override 193 public void notifyTimeout(DCCppMessage msg) { 194 log.debug("Notified of timeout on message '{}' , {} retries available.", msg, msg.getRetries()); 195 if (msg.getRetries() > 0) { 196 msg.setRetries(msg.getRetries() - 1); 197 tc.sendDCCppMessage(msg, this); 198 } 199 } 200 201 /** {@inheritDoc} */ 202 @Override 203 public boolean allowMultipleAdditions(@Nonnull String systemName) { 204 return true; 205 } 206 207 /** 208 * {@inheritDoc} 209 */ 210 @Override 211 public NameValidity validSystemNameFormat(@Nonnull String systemName) { 212 return (getBitFromSystemName(systemName) != -1) ? NameValidity.VALID : NameValidity.INVALID; 213 } 214 215 /** 216 * {@inheritDoc} 217 */ 218 @Override 219 @Nonnull 220 public String validateSystemNameFormat(@Nonnull String systemName, @Nonnull Locale locale) { 221 return validateIntegerSystemNameFormat(systemName, 0, MAX_TURNOUT_ADDRESS, locale); 222 } 223 224 /** 225 * Get the bit address from the system name. 226 * 227 * @param systemName a valid Turnout System Name 228 * @return the turnout number extracted from the system name 229 */ 230 public int getBitFromSystemName(String systemName) { 231 try { 232 validateSystemNameFormat(systemName, Locale.getDefault()); 233 } catch (IllegalArgumentException ex) { 234 return -1; 235 } 236 return Integer.parseInt(systemName.substring(getSystemNamePrefix().length())); 237 } 238 239 /** {@inheritDoc} */ 240 @Override 241 public String getEntryToolTip() { 242 return Bundle.getMessage("AddOutputEntryToolTip"); 243 } 244 245 private final static Logger log = LoggerFactory.getLogger(DCCppTurnoutManager.class); 246 247}