001package jmri.jmrix.cmri.serial; 002 003import java.util.Locale; 004 005import javax.annotation.Nonnull; 006 007import jmri.*; 008import jmri.jmrix.cmri.CMRISystemConnectionMemo; 009import jmri.managers.AbstractTurnoutManager; 010import jmri.util.swing.JmriJOptionPane; 011 012/** 013 * Implement turnout manager for CMRI serial systems. 014 * <p> 015 * System names are "CTnnn", where C is the user configurable system prefix, 016 * nnn is the turnout number without padding. 017 * 018 * @author Bob Jacobsen Copyright (C) 2003 019 */ 020public class SerialTurnoutManager extends AbstractTurnoutManager { 021 022 public SerialTurnoutManager(CMRISystemConnectionMemo memo) { 023 super(memo); 024 } 025 026 /** 027 * {@inheritDoc} 028 */ 029 @Override 030 @Nonnull 031 public CMRISystemConnectionMemo getMemo() { 032 return (CMRISystemConnectionMemo) memo; 033 } 034 035 /** 036 * {@inheritDoc} 037 */ 038 @Nonnull 039 @Override 040 protected Turnout createNewTurnout(@Nonnull String systemName, String userName) throws IllegalArgumentException { 041 // validate the system name, and normalize it 042 String sName = getMemo().normalizeSystemName(systemName); 043 if (sName.isEmpty()) { 044 // system name is not valid 045 throw new IllegalArgumentException("Cannot create System Name from " + systemName); 046 } 047 // does this turnout already exist 048 Turnout t = getBySystemName(sName); 049 if (t != null) { 050 return t; 051 } 052 // check under alternate name 053 String altName = getMemo().convertSystemNameToAlternate(sName); 054 t = getBySystemName(altName); 055 if (t != null) { 056 return t; 057 } 058 059 // check if the addressed output bit is available 060 int nAddress = getMemo().getNodeAddressFromSystemName(sName); 061 if (nAddress == -1) { 062 throw new IllegalArgumentException("Cannot get Node Address from System Name " + systemName + " " + sName); 063 } 064 int bitNum = getMemo().getBitFromSystemName(sName); 065 if (bitNum == 0) { 066 throw new IllegalArgumentException("Cannot get Bit from System Name " + systemName + " " + sName); 067 } 068 String conflict = getMemo().isOutputBitFree(nAddress, bitNum); 069 if ((!conflict.isEmpty()) && (!conflict.equals(sName))) { 070 log.error("{} assignment conflict with {}.", sName, conflict); 071 throw new IllegalArgumentException(Bundle.getMessage("ErrorAssignDialog", bitNum, conflict)); 072 } 073 074 // create the turnout 075 t = new SerialTurnout(sName, userName, getMemo()); 076 077 // does system name correspond to configured hardware 078 if (!getMemo().validSystemNameConfig(sName, 'T', getMemo().getTrafficController())) { 079 // system name does not correspond to configured hardware 080 log.warn("Turnout '{}' refers to an undefined Serial Node.", sName); 081 } 082 return t; 083 } 084 085 /** 086 * Get from the user, the number of addressed bits used to control a 087 * turnout. 088 * <p> 089 * Normally this is 1, and the default routine returns 1 090 * automatically. Turnout Managers for systems that can handle multiple 091 * control bits should override this method with one which asks the user to 092 * specify the number of control bits. If the user specifies more than one 093 * control bit, this method should check if the additional bits are 094 * available (not assigned to another object). If the bits are not 095 * available, this method should return 0 for number of control bits, after 096 * informing the user of the problem. This function is called whenever a new 097 * turnout is defined in the Turnout table. It can also be used to set up 098 * other turnout control options, such as pulsed control of turnout 099 * machines. 100 */ 101 @Override 102 public int askNumControlBits(@Nonnull String systemName) { 103 104 // ask user how many bits should control the turnout - 1 or 2 105 int iNum = selectNumberOfControlBits(); 106 if (iNum == JmriJOptionPane.CLOSED_OPTION) { 107 /* user cancelled without selecting an option */ 108 iNum = 1; 109 log.warn("User cancelled without selecting number of output bits. Defaulting to 1."); 110 } else { 111 iNum = iNum + 1; 112 } 113 114 if (iNum == 2) { 115 // check if the second output bit is available 116 int nAddress = getMemo().getNodeAddressFromSystemName(systemName); 117 if (nAddress == -1) { 118 return 0; 119 } 120 int bitNum = getMemo().getBitFromSystemName(systemName); 121 if (bitNum == 0) { 122 return 0; 123 } 124 bitNum = bitNum + 1; 125 String conflict = getMemo().isOutputBitFree(nAddress, bitNum); 126 if (!conflict.equals("")) { 127 log.error("Assignment conflict with {}. Turnout not created.", conflict); 128 notifySecondBitConflict(conflict, bitNum); 129 return 0; 130 } 131 } 132 133 return (iNum); 134 } 135 136 /** 137 * Get from the user, the type of output to be used bits to control a 138 * turnout. 139 * <p> 140 * Normally this is 0 for 'steady state' control, and the default 141 * routine returns 0 automatically. Turnout Managers for systems that can 142 * handle pulsed control as well as steady state control should override 143 * this method with one which asks the user to specify the type of control 144 * to be used. The routine should return 0 for 'steady state' control, or n 145 * for 'pulsed' control, where n specifies the duration of the pulse 146 * (normally in seconds). 147 */ 148 @Override 149 public int askControlType(@Nonnull String systemName) { 150 // ask if user wants 'steady state' output (stall motors, e.g., Tortoises) or 151 // 'pulsed' output (some turnout controllers). 152 int iType = selectOutputType(); 153 if (iType == JmriJOptionPane.CLOSED_OPTION) { 154 /* user cancelled without selecting an output type */ 155 iType = 0; 156 log.warn("User cancelled without selecting output type. Defaulting to 'steady state'."); 157 } 158 // Note: If the user selects 'pulsed', this routine defaults to 1 second. 159 return (iType); 160 } 161 162 /** 163 * Public method to allow user to specify one or two output bits for turnout 164 * control. 165 * 166 * @return JmriJOptionPane.CLOSED_OPTION if the user cancelled without selecting. 167 * 0 if the user selected BitOption1, 168 * 1 if the user selected BitOption2. 169 */ 170 public int selectNumberOfControlBits() { 171 return JmriJOptionPane.showOptionDialog(null, 172 Bundle.getMessage("QuestionBitsDialog"), 173 Bundle.getMessage("CmriTurnoutTitle"), JmriJOptionPane.DEFAULT_OPTION, 174 JmriJOptionPane.QUESTION_MESSAGE, 175 null, new String[]{Bundle.getMessage("BitOption1"), Bundle.getMessage("BitOption2")}, Bundle.getMessage("BitOption1")); 176 } 177 178 /** 179 * Public method to allow user to specify pulsed or steady state for two 180 * output bits for turnout control. 181 * 182 * @return JmriJOptionPane.CLOSED_OPTION if the user cancelled without selecting. 183 * 0 if the user selected PulsedOptionSteady, 184 * 1 if the user selected PulsedOptionPulsed. 185 */ 186 public int selectOutputType() { 187 return JmriJOptionPane.showOptionDialog(null, 188 Bundle.getMessage("QuestionPulsedDialog"), 189 Bundle.getMessage("CmriBitsTitle"), JmriJOptionPane.DEFAULT_OPTION, 190 JmriJOptionPane.QUESTION_MESSAGE, 191 null, new String[]{Bundle.getMessage("PulsedOptionSteady"), Bundle.getMessage("PulsedOptionPulsed")}, 192 Bundle.getMessage("PulsedOptionSteady")); 193 } 194 195 /** 196 * Public method to notify user when the second bit of a proposed two output 197 * bit turnout has a conflict with another assigned bit. 198 * @param conflict human readable name of turnout with conflict. 199 * @param bitNum conflict bit number. 200 */ 201 public void notifySecondBitConflict(String conflict, int bitNum) { 202 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("ErrorAssign2Dialog", bitNum, conflict) + "\n" + 203 Bundle.getMessage("ErrorAssignLine2X", Bundle.getMessage("BeanNameTurnout")), 204 Bundle.getMessage("ErrorAssignTitle"), 205 JmriJOptionPane.INFORMATION_MESSAGE ); 206 } 207 208 /** 209 * Turnout format is more than a simple format. 210 */ 211 @Override 212 public boolean allowMultipleAdditions(@Nonnull String systemName) { 213 return true; 214 } 215 216 /** 217 * {@inheritDoc} 218 */ 219 @Override 220 public boolean isNumControlBitsSupported(@Nonnull String systemName) { 221 return true; 222 } 223 224 /** 225 * {@inheritDoc} 226 */ 227 @Override 228 public boolean isControlTypeSupported(@Nonnull String systemName) { 229 return true; 230 } 231 232 /** 233 * {@inheritDoc} 234 */ 235 @Override 236 public String createSystemName(@Nonnull String curAddress, @Nonnull String prefix) throws JmriException { 237 int seperator = 0; 238 String tmpSName; 239 240 if (curAddress.contains(":")) { 241 // Address format passed is in the form node:address 242 seperator = curAddress.indexOf(":"); 243 nAddress = Integer.parseInt(curAddress.substring(0, seperator)); 244 // check for non-numerical chars 245 try { 246 bitNum = Integer.parseInt(curAddress.substring(seperator + 1)); 247 } catch (NumberFormatException ex) { 248 throw new JmriException("Part 2 of " + curAddress + " is not an integer"); 249 } 250 tmpSName = getMemo().makeSystemName("T", nAddress, bitNum); 251 } else if (curAddress.contains("B") || (curAddress.contains("b"))) { 252 curAddress = curAddress.toUpperCase(); 253 try { 254 //We do this to simply check that we have numbers in the correct places ish 255 Integer.parseInt(curAddress.substring(0, 1)); 256 int b = (curAddress.toUpperCase()).indexOf("B") + 1; 257 Integer.parseInt(curAddress.substring(b)); 258 } catch (NumberFormatException ex) { 259 throw new JmriException("Unable to convert " + curAddress + " to a valid Hardware Address"); 260 } 261 tmpSName = prefix + typeLetter() + curAddress; 262 bitNum = getMemo().getBitFromSystemName(tmpSName); 263 nAddress = getMemo().getNodeAddressFromSystemName(tmpSName); 264 } else { 265 try { 266 // We do this to simply check that the value passed is a number! 267 Integer.parseInt(curAddress); 268 } catch (NumberFormatException ex) { 269 throw new JmriException("Address " + curAddress + " is not an integer"); 270 } 271 tmpSName = prefix + "T" + curAddress; 272 bitNum = getMemo().getBitFromSystemName(tmpSName); 273 nAddress = getMemo().getNodeAddressFromSystemName(tmpSName); 274 } 275 return (tmpSName); 276 } 277 278 private int bitNum = 0; 279 private int nAddress = 0; 280 281 /** 282 * {@inheritDoc} 283 */ 284 @Override 285 @Nonnull 286 public String validateSystemNameFormat(@Nonnull String systemName, @Nonnull Locale locale) throws NamedBean.BadSystemNameException { 287 systemName = validateSystemNamePrefix(systemName, locale); 288 return getMemo().validateSystemNameFormat(super.validateSystemNameFormat(systemName, locale), typeLetter(), locale); 289 } 290 291 /** 292 * {@inheritDoc} 293 */ 294 @Override 295 public NameValidity validSystemNameFormat(@Nonnull String systemName) { 296 return getMemo().validSystemNameFormat(systemName, typeLetter()); 297 } 298 299 /** 300 * {@inheritDoc} 301 */ 302 @Override 303 public String getEntryToolTip() { 304 return Bundle.getMessage("AddOutputEntryToolTip"); 305 } 306 307 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SerialTurnoutManager.class); 308 309}