001package jmri.jmrix.nce; 002 003import jmri.NmraPacket; 004import jmri.PushbuttonPacket; 005import jmri.Turnout; 006import jmri.implementation.AbstractTurnout; 007import org.slf4j.Logger; 008import org.slf4j.LoggerFactory; 009 010/** 011 * Implement a Turnout via NCE communications. 012 * <p> 013 * This object doesn't listen to the NCE communications. This is because it 014 * should be the only object that is sending messages for this turnout; more 015 * than one Turnout object pointing to a single device is not allowed. 016 * 017 * @author Bob Jacobsen Copyright (C) 2001 018 * @author Daniel Boudreau (C) 2007 019 */ 020public class NceTurnout extends AbstractTurnout { 021 022 NceTrafficController tc = null; 023 String prefix = ""; 024 025 /** 026 * NCE turnouts use the NMRA number (0-2044) as their numerical 027 * identification. 028 * @param tc traffic controller for connection 029 * @param p system connection prefix 030 * @param i NMRA turnout number 031 */ 032 public NceTurnout(NceTrafficController tc, String p, int i) { 033 super(p + "T" + i); 034 this.tc = tc; 035 this.prefix = p + "T"; 036 _number = i; 037 if (_number < NmraPacket.accIdLowLimit || _number > NmraPacket.accIdHighLimit) { 038 throw new IllegalArgumentException("Turnout value: " + _number 039 + " not in the range " + NmraPacket.accIdLowLimit + " to " 040 + NmraPacket.accIdHighLimit); 041 } 042 // At construction, register for messages 043 initialize(); 044 } 045 046 private void initialize() { 047 synchronized(lock) { 048 numNtTurnouts++; // increment the total number of NCE turnouts 049 // update feedback modes, MONITORING requires PowerPro system with new EPROM 050 if (tc.getCommandOptions() >= NceTrafficController.OPTION_2006 && tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_NONE) { 051 if (modeNames == null) { 052 if (_validFeedbackNames.length != _validFeedbackModes.length) { 053 log.error("int and string feedback arrays different length"); 054 } 055 modeNames = new String[_validFeedbackNames.length + 1]; 056 modeValues = new int[_validFeedbackNames.length + 1]; 057 for (int i = 0; i < _validFeedbackNames.length; i++) { 058 modeNames[i] = _validFeedbackNames[i]; 059 modeValues[i] = _validFeedbackModes[i]; 060 } 061 modeNames[_validFeedbackNames.length] = "MONITORING"; 062 modeValues[_validFeedbackNames.length] = MONITORING; 063 } 064 _validFeedbackTypes |= MONITORING; 065 _validFeedbackNames = modeNames; 066 _validFeedbackModes = modeValues; 067 } 068 _enableCabLockout = true; 069 _enablePushButtonLockout = true; 070 } 071 } 072 073 private static final Object lock = new Object(); 074 075 static String[] modeNames = null; 076 static int[] modeValues = null; 077 private static int numNtTurnouts = 0; 078 079 public int getNumber() { 080 return _number; 081 } 082 083 public static int getNumNtTurnouts() { 084 return numNtTurnouts; 085 } 086 087 /** 088 * {@inheritDoc} 089 */ 090 @Override 091 protected void forwardCommandChangeToLayout(int newState) { 092 // implementing classes will typically have a function/listener to get 093 // updates from the layout, which will then call 094 // public void firePropertyChange(String propertyName, 095 // Object oldValue, 096 // Object newValue) 097 // _once_ if anything has changed state (or set the commanded state directly) 098 099 // sort out states 100 if ((newState & Turnout.CLOSED) != 0) { 101 // first look for the double case, which we can't handle 102 if ((newState & Turnout.THROWN) != 0) { 103 // this is the disaster case! 104 log.error("Cannot command both CLOSED and THROWN {}", newState); 105 return; 106 } else { 107 // send a CLOSED command 108 sendMessage(!getInverted()); 109 } 110 } else { 111 // send a THROWN command 112 sendMessage(getInverted()); 113 } 114 } 115 116 /** 117 * Send a message to the layout to lock or unlock the turnout pushbuttons if 118 * true, pushbutton lockout enabled. 119 * {@inheritDoc} 120 */ 121 @Override 122 protected void turnoutPushbuttonLockout(boolean pushButtonLockout) { 123 log.debug("Send command to {} Pushbutton {}{}", 124 pushButtonLockout ? "Lock" : "Unlock", prefix, _number); 125 126 byte[] bl = PushbuttonPacket.pushbuttonPkt(prefix, _number, pushButtonLockout); 127 NceMessage m = NceMessage.sendPacketMessage(tc, bl); 128 tc.sendNceMessage(m, null); 129 } 130 131 // data members 132 int _number; // turnout number 133 134 /** 135 * Set the turnout known state to reflect what's been observed from the 136 * command station polling. A change there means that somebody commanded a 137 * state change (by using a throttle), and that command has 138 * already taken effect. Hence we use "newCommandedState" to indicate it's 139 * taken place. Must be followed by "newKnownState" to complete the turnout 140 * action. 141 * 142 * @param state Observed state, updated state from command station 143 */ 144 synchronized void setCommandedStateFromCS(int state) { 145 if ((getFeedbackMode() != MONITORING)) { 146 return; 147 } 148 149 newCommandedState(state); 150 } 151 152 /** 153 * Set the turnout known state to reflect what's been observed from the 154 * command station polling. A change there means that somebody commanded a 155 * state change (by using a throttle), and that command has 156 * already taken effect. Hence we use "newKnownState" to indicate it's taken 157 * place. 158 * 159 * @param state Observed state, updated state from command station 160 */ 161 synchronized void setKnownStateFromCS(int state) { 162 if ((getFeedbackMode() != MONITORING)) { 163 return; 164 } 165 166 newKnownState(state); 167 } 168 169 /** 170 * NCE turnouts can be inverted. 171 */ 172 @Override 173 public boolean canInvert() { 174 return true; 175 } 176 /** 177 * NCE turnouts can provide both modes when properly configured. 178 * 179 * @return Both cab and pushbutton (decoder) modes 180 */ 181 @Override 182 public int getPossibleLockModes() { return CABLOCKOUT | PUSHBUTTONLOCKOUT ; } 183 184 /** 185 * NCE turnouts support two types of lockouts, pushbutton and cab. Cab 186 * lockout requires the feedback mode to be Monitoring. 187 * {@inheritDoc} 188 */ 189 @Override 190 public boolean canLock(int turnoutLockout) { 191 // can not lock if using a USB 192 if (tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE) { 193 return false; 194 } 195 // check to see if push button lock is enabled and valid decoder 196 String dn = getDecoderName(); 197 if ((turnoutLockout & PUSHBUTTONLOCKOUT) != 0 && _enablePushButtonLockout 198 && dn != null && !dn.equals(PushbuttonPacket.unknown)) { 199 return true; 200 } 201 // check to see if cab lockout is enabled 202 if ((turnoutLockout & CABLOCKOUT) != 0 203 && getFeedbackMode() == MONITORING && _enableCabLockout) { 204 return true; 205 } else { 206 return false; 207 } 208 } 209 210 /** 211 * Control which turnout locks are enabled. 212 * {@inheritDoc} 213 */ 214 @Override 215 public void enableLockOperation(int turnoutLockout, boolean enabled) { 216 if ((turnoutLockout & CABLOCKOUT) != 0) { 217 if (enabled) { 218 _enableCabLockout = true; 219 } else { 220 // unlock cab before disabling 221 _cabLockout = false; 222 _enableCabLockout = false; 223 // pushbutton lockout has to be enabled if cab lockout is disabled 224 _enablePushButtonLockout = true; 225 } 226 } 227 if ((turnoutLockout & PUSHBUTTONLOCKOUT) != 0) { 228 if (enabled) { 229 _enablePushButtonLockout = true; 230 } else { 231 // only time we can disable pushbuttons is if we can lockout cabs 232 if (getFeedbackMode() != MONITORING) { 233 return; 234 } 235 // pushbutton lockout has to be enabled if cab lockout is disabled 236 if (_enableCabLockout) { 237 _enablePushButtonLockout = false; 238 } 239 } 240 } 241 } 242 243 protected void sendMessage(boolean closed) { 244 // get the packet 245 // dBoudreau Added support for new accessory binary command 246 247 if (tc.getCommandOptions() >= NceTrafficController.OPTION_2006) { 248 249 byte[] bl = NceBinaryCommand.accDecoder(_number, closed); 250 251 if (log.isDebugEnabled()) { 252 log.debug("Command: {} {} {} {} {}", 253 Integer.toHexString(0xFF & bl[0]), 254 Integer.toHexString(0xFF & bl[1]), 255 Integer.toHexString(0xFF & bl[2]), 256 Integer.toHexString(0xFF & bl[3]), 257 Integer.toHexString(0xFF & bl[4])); 258 } 259 260 NceMessage m = NceMessage.createBinaryMessage(tc, bl); 261 262 tc.sendNceMessage(m, null); 263 264 } else { 265 266 byte[] bl = NmraPacket.accDecoderPkt(_number, closed); 267 268 if (log.isDebugEnabled()) { 269 log.debug("packet: {} {} {}", 270 Integer.toHexString(0xFF & bl[0]), 271 Integer.toHexString(0xFF & bl[1]), 272 Integer.toHexString(0xFF & bl[2])); 273 } 274 275 NceMessage m = NceMessage.sendPacketMessage(tc, bl); 276 277 tc.sendNceMessage(m, null); 278 } 279 } 280 281 private final static Logger log = LoggerFactory.getLogger(NceTurnout.class); 282 283}