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 042 tc.sendDCCppMessage(DCCppMessage.makeTurnoutIDsMsg(), this); 043 } 044 045 /** 046 * {@inheritDoc} 047 */ 048 @Override 049 @Nonnull 050 public DCCppSystemConnectionMemo getMemo() { 051 return (DCCppSystemConnectionMemo) memo; 052 } 053 054 // DCCpp-specific methods 055 056 /** 057 * {@inheritDoc} 058 */ 059 @Nonnull 060 @Override 061 protected Turnout createNewTurnout(@Nonnull String systemName, String userName) throws IllegalArgumentException { 062 // check if the output bit is available 063 int bitNum = getBitFromSystemName(systemName); 064 if (bitNum < 0) { 065 throw new IllegalArgumentException("Cannot get Bit from System Name " + systemName); 066 } 067 // make the new Turnout object 068 Turnout t = new DCCppTurnout(getSystemPrefix(), bitNum, tc); 069 t.setUserName(userName); 070 return t; 071 } 072 073 /** 074 * {@inheritDoc} 075 * Listen for turnouts, creating them as needed. 076 */ 077 @Override 078 public void message(DCCppReply l) { 079 if (l.isTurnoutReply()) { 080 log.debug("received Turnout Reply message: '{}'", l); 081 // parse message type 082 int addr = l.getTOIDInt(); 083 if (addr >= 0) { 084 // check to see if the address has been operated before 085 // continuing. 086 log.debug("message has address: {}", addr); 087 // reach here for switch command; make sure we know 088 // about this one 089 String s = getSystemNamePrefix() + addr; 090 DCCppTurnout found = (DCCppTurnout) getBySystemName(s); 091 if ( found == null) { 092 // need to create a new one, set some attributes, 093 // and send the message on to the newly created object. 094 DCCppTurnout t = (DCCppTurnout) provideTurnout(s); 095 t.setFeedbackMode(Turnout.MONITORING); 096 t.initmessage(l); 097 } else { 098 // The turnout exists, forward this message to the 099 // turnout 100 found.message(l); 101 } 102 } 103 } else if (l.isOutputReply()) { 104 log.debug("received Output Reply message: '{}'", l); 105 // parse message type 106 int addr = l.getOutputNumInt(); 107 if (addr >= 0) { 108 // check to see if the address has been operated before 109 // continuing. 110 log.debug("message has address: {}", addr); 111 // reach here for switch command; make sure we know 112 // about this one 113 String s = getSystemNamePrefix() + addr; 114 DCCppTurnout found = (DCCppTurnout) getBySystemName(s); 115 if (found == null) { 116 // need to create a new one, and send the message on 117 // to the newly created object. 118 DCCppTurnout t = (DCCppTurnout) provideTurnout(s); 119 t.setFeedbackMode(Turnout.EXACT); 120 t.initmessage(l); 121 } else { 122 // The turnout exists, forward this message to the 123 // turnout 124 found.message(l); 125 } 126 } 127 } else if (l.isTurnoutIDsReply()) { 128 log.debug("received Turnout ID List message: '{}'", l); 129 ArrayList<Integer> ids = l.getTurnoutIDList(); 130 for (Integer id : ids) { //request details for each id included in this message 131 tc.sendDCCppMessage(DCCppMessage.makeTurnoutIDMsg(id), this); 132 tc.sendDCCppMessage(DCCppMessage.makeTurnoutImplMsg(id), this); 133 } 134 } else if (l.isTurnoutIDReply()) { 135 log.debug("received Turnout ID Detail message: '{}'", l); 136 // parse message type 137 int addr = l.getTOIDInt(); 138 if (addr >= 0) { 139 log.debug("message has address: {}", addr); 140 String s = getSystemNamePrefix() + addr; 141 DCCppTurnout found = (DCCppTurnout) getBySystemName(s); 142 if ( found == null) { //create new turnout 143 DCCppTurnout t = (DCCppTurnout) provideTurnout(s); 144 t.setFeedbackMode(Turnout.MONITORING); 145 if (!l.getTurnoutDescString().isEmpty()) { 146 t.setUserName(l.getTurnoutDescString()); //add username if available 147 } 148 t.initmessage(l); //forward message to turnout 149 } else { // The turnout already exists 150 if (!l.getTurnoutDescString().isEmpty() && found.getUserName()==null) { 151 found.setUserName(l.getTurnoutDescString()); //set userName if needed and available 152 } 153 found.message(l); //forward message to turnout 154 } 155 } 156 } 157 } 158 159 /** 160 * Get text to be used for the Turnout.CLOSED state in user communication. 161 * Allows text other than "CLOSED" to be use with certain hardware system to 162 * represent the Turnout.CLOSED state. 163 */ 164 @Override 165 @Nonnull 166 public String getClosedText() { 167 return Bundle.getMessage("TurnoutStateClosed"); 168 } 169 170 /** 171 * Get text to be used for the Turnout.THROWN state in user communication. 172 * Allows text other than "THROWN" to be use with certain hardware system to 173 * represent the Turnout.THROWN state. 174 */ 175 @Override 176 @Nonnull 177 public String getThrownText() { 178 return Bundle.getMessage("TurnoutStateThrown"); 179 } 180 181 /** 182 * Listen for the outgoing messages (to the command station) 183 */ 184 @Override 185 public void message(DCCppMessage l) { 186 } 187 188 // Handle message timeout notification 189 // If the message still has retries available, reduce retries and send it back to the traffic controller. 190 @Override 191 public void notifyTimeout(DCCppMessage msg) { 192 log.debug("Notified of timeout on message '{}' , {} retries available.", msg, msg.getRetries()); 193 if (msg.getRetries() > 0) { 194 msg.setRetries(msg.getRetries() - 1); 195 tc.sendDCCppMessage(msg, this); 196 } 197 } 198 199 /** {@inheritDoc} */ 200 @Override 201 public boolean allowMultipleAdditions(@Nonnull String systemName) { 202 return true; 203 } 204 205 /** 206 * {@inheritDoc} 207 */ 208 @Override 209 public NameValidity validSystemNameFormat(@Nonnull String systemName) { 210 return (getBitFromSystemName(systemName) != -1) ? NameValidity.VALID : NameValidity.INVALID; 211 } 212 213 /** 214 * {@inheritDoc} 215 */ 216 @Override 217 @Nonnull 218 public String validateSystemNameFormat(@Nonnull String systemName, @Nonnull Locale locale) { 219 return validateIntegerSystemNameFormat(systemName, 0, MAX_TURNOUT_ADDRESS, locale); 220 } 221 222 /** 223 * Get the bit address from the system name. 224 * 225 * @param systemName a valid Turnout System Name 226 * @return the turnout number extracted from the system name 227 */ 228 public int getBitFromSystemName(String systemName) { 229 try { 230 validateSystemNameFormat(systemName, Locale.getDefault()); 231 } catch (IllegalArgumentException ex) { 232 return -1; 233 } 234 return Integer.parseInt(systemName.substring(getSystemNamePrefix().length())); 235 } 236 237 /** {@inheritDoc} */ 238 @Override 239 public String getEntryToolTip() { 240 return Bundle.getMessage("AddOutputEntryToolTip"); 241 } 242 243 private final static Logger log = LoggerFactory.getLogger(DCCppTurnoutManager.class); 244 245}