001package jmri.jmrix.ecos; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import jmri.Turnout; 005import jmri.implementation.AbstractTurnout; 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008 009/** 010 * Implement a Turnout via ECoS communications. 011 * <p> 012 * This object doesn't listen to the Ecos communications. This is because it 013 * should be the only object that is sending messages for this turnout; more 014 * than one Turnout object pointing to a single device is not allowed. 015 * 016 * @author Bob Jacobsen Copyright (C) 2001 017 * @author Daniel Boudreau (C) 2007 018 */ 019public class EcosTurnout extends AbstractTurnout 020 implements EcosListener { 021 022 String prefix; 023 024 int objectNumber = 0; 025 boolean masterObjectNumber = true; 026 String slaveAddress; 027 int extended = 0; 028 029 /** 030 * ECoS turnouts use the NMRA number (0-2044) as their numerical 031 * identification in the system name. 032 * 033 * @param number DCC address of the turnout 034 * @param prefix system prefix 035 * @param etc system connection traffic controller 036 * @param etm ecos turnout manager 037 */ 038 public EcosTurnout(int number, String prefix, EcosTrafficController etc, EcosTurnoutManager etm) { 039 super(prefix + "T" + number); 040 _number = number; 041 /*if (_number < NmraPacket.accIdLowLimit || _number > NmraPacket.accIdHighLimit) { 042 throw new IllegalArgumentException("Turnout value: " + _number 043 + " not in the range " + NmraPacket.accIdLowLimit + " to " 044 + NmraPacket.accIdHighLimit); 045 }*/ 046 this.prefix = prefix; 047 tc = etc; 048 tm = etm; 049 /* All messages from the ECoS regarding turnout status updates 050 are initally handled by the TurnoutManager, this then forwards the message 051 on to the correct Turnout */ 052 053 // update feedback modes 054 _validFeedbackTypes |= MONITORING | EXACT | INDIRECT; 055 _activeFeedbackType = MONITORING; 056 057 // if needed, create the list of feedback mode 058 // names with additional LocoNet-specific modes 059 if (modeNames == null) { 060 initFeedbackModes(); 061 } 062 _validFeedbackNames = modeNames; 063 _validFeedbackModes = modeValues; 064 } 065 066 @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", 067 justification = "Only used during creation of 1st turnout") 068 private void initFeedbackModes() { 069 if (_validFeedbackNames.length != _validFeedbackModes.length) { 070 log.error("int and string feedback arrays different length"); 071 } 072 String[] tempModeNames = new String[_validFeedbackNames.length + 3]; 073 int[] tempModeValues = new int[_validFeedbackNames.length + 3]; 074 for (int i = 0; i < _validFeedbackNames.length; i++) { 075 tempModeNames[i] = _validFeedbackNames[i]; 076 tempModeValues[i] = _validFeedbackModes[i]; 077 } 078 tempModeNames[_validFeedbackNames.length] = "MONITORING"; 079 tempModeValues[_validFeedbackNames.length] = MONITORING; 080 tempModeNames[_validFeedbackNames.length + 1] = "INDIRECT"; 081 tempModeValues[_validFeedbackNames.length + 1] = INDIRECT; 082 tempModeNames[_validFeedbackNames.length + 2] = "EXACT"; 083 tempModeValues[_validFeedbackNames.length + 2] = EXACT; 084 085 modeNames = tempModeNames; 086 modeValues = tempModeValues; 087 } 088 089 static String[] modeNames = null; 090 static int[] modeValues = null; 091 092 EcosTrafficController tc; 093 EcosTurnoutManager tm; 094 /*Extended is used to indicate that this Ecos accessory has a secondary address assigned to it. 095 the value determines the symbol/icon used on the ecos. 096 2 - Three Way Point 097 4 - Double Slip*/ 098 public static final int THREEWAY = 2; 099 public static final int DOUBLESLIP = 4; 100 101 void setExtended(int e) { 102 extended = e; 103 } 104 105 void setObjectNumber(int o) { 106 objectNumber = o; 107 } 108 109 void setSlaveAddress(int o) { 110 slaveAddress = prefix + "T" + o; 111 } 112 113 void setMasterObjectNumber(boolean o) { 114 masterObjectNumber = o; 115 } 116 117 public int getNumber() { 118 return _number; 119 } 120 121 public int getObject() { 122 return objectNumber; 123 } 124 125 public int getExtended() { 126 return extended; 127 } 128 129 public String getSlaveAddress() { 130 return slaveAddress; 131 } 132 133 /** 134 * {@inheritDoc} 135 */ 136 @Override 137 protected void forwardCommandChangeToLayout(int newState) { 138 // implementing classes will typically have a function/listener to get 139 // updates from the layout, which will then call 140 // public void firePropertyChange(String propertyName, 141 // Object oldValue, 142 // Object newValue) 143 // _once_ if anything has changed state (or set the commanded state directly) 144 145 // sort out states 146 if ((newState & Turnout.CLOSED) != 0) { 147 // first look for the double case, which we can't handle 148 if ((newState & Turnout.THROWN) != 0) { 149 // this is the disaster case! 150 log.error("Cannot command both CLOSED and THROWN {}", newState); 151 return; 152 } else { 153 // send a CLOSED command 154 sendMessage(true ^ getInverted()); 155 } 156 } else { 157 // send a THROWN command 158 sendMessage(false ^ getInverted()); 159 } 160 } 161 162 // data members 163 int _number; // turnout number 164 165 /** 166 * Set the turnout known state to reflect what's been observed from the 167 * command station messages. A change there means that somebody commanded a 168 * state change (by using a throttle), and that command has 169 * already taken effect. Hence we use "newCommandedState" to indicate it's 170 * taken place. Must be followed by "newKnownState" to complete the turnout 171 * action. 172 * 173 * @param state Observed state, updated state from command station 174 */ 175 synchronized void setCommandedStateFromCS(int state) { 176 if ((getFeedbackMode() != MONITORING)) { 177 return; 178 } 179 180 newCommandedState(state); 181 } 182 183 /** 184 * Set the turnout known state to reflect what's been observed from the 185 * command station messages. A change there means that somebody commanded a 186 * state change (by using a throttle), and that command has 187 * already taken effect. Hence we use "newKnownState" to indicate it's taken 188 * place. 189 * 190 * @param state Observed state, updated state from command station 191 */ 192 synchronized void setKnownStateFromCS(int state) { 193 if ((getFeedbackMode() != MONITORING)) { 194 return; 195 } 196 197 newKnownState(state); 198 } 199 200 @Override 201 public void turnoutPushbuttonLockout(boolean b) { 202 } 203 204 /** 205 * @return ECoS turnouts can be inverted 206 */ 207 @Override 208 public boolean canInvert() { 209 return true; 210 } 211 212 /** 213 * Tell the layout to go to new state. 214 * 215 * @param closed State of the turnout to be sent to the command station 216 */ 217 protected void sendMessage(boolean closed) { 218 newKnownState(Turnout.UNKNOWN); 219 if (getInverted()) { 220 closed = !closed; 221 } 222 if ((masterObjectNumber) && (extended == 0)) { 223 EcosMessage m; 224 // get control 225 m = new EcosMessage("request(" + objectNumber + ", control)"); 226 tc.sendEcosMessage(m, null); 227 // set state 228 m = new EcosMessage("set(" + objectNumber + ", state[" + (closed ? "0" : "1") + "])"); 229 tc.sendEcosMessage(m, null); 230 // release control 231 m = new EcosMessage("release(" + objectNumber + ", control)"); 232 tc.sendEcosMessage(m, null); 233 } else { //we have a 3 way or double slip! 234 //Working upon the basis that if the materObjectNumber is false than this is the second 235 //decoder address assigned, while if it is true then we are the first decoder address. 236 boolean firststate; 237 boolean secondstate; 238 if (!masterObjectNumber) { 239 //Here we are dealing with the second address 240 int turnaddr = _number - 1; 241 Turnout t = tm.getTurnout(prefix + "T" + turnaddr); 242 secondstate = closed; 243 if (t == null){ 244 log.error("Unable to locate second Turnout address {}",turnaddr); 245 return; 246 } else { 247 if (t.getKnownState() == CLOSED) { 248 firststate = true; 249 } else { 250 firststate = false; 251 } 252 } 253 254 } else { 255 Turnout t = tm.getTurnout(slaveAddress); 256 firststate = closed; 257 if (t == null){ 258 log.error("Unable to locate slave Turnout address {}",slaveAddress); 259 return; 260 } else { 261 262 if (t.getKnownState() == CLOSED) { 263 secondstate = true; 264 } else { 265 secondstate = false; 266 } 267 } 268 } 269 int setState = 0; 270 if (extended == THREEWAY) { 271 if ((firststate) && (secondstate)) { 272 setState = 0; 273 } else if ((firststate) && (!secondstate)) { 274 setState = 1; 275 } else { 276 setState = 2; 277 } 278 } else if (extended == DOUBLESLIP) { 279 if ((firststate) && (secondstate)) { 280 setState = 0; 281 } else if ((!firststate) && (!secondstate)) { 282 setState = 1; 283 } else if ((!firststate) && (secondstate)) { 284 setState = 2; 285 } else { 286 setState = 3; 287 } 288 } 289 290 if (setState == 99) { 291 // log.debug("Invalid selection old state " + getKnownState() + " " + getCommandedState()); 292 if (closed) { 293 setCommandedState(THROWN); 294 } else { 295 setCommandedState(CLOSED); 296 } 297 // log.debug("After - " + getKnownState() + " " + getCommandedState() + " " + "Is consistant " + isConsistentState()); 298 } else { 299 300 EcosMessage m = new EcosMessage("request(" + objectNumber + ", control)"); 301 tc.sendEcosMessage(m, this); 302 // set state 303 m = new EcosMessage("set(" + objectNumber + ", state[" + setState + "])"); 304 tc.sendEcosMessage(m, this); 305 // release control 306 m = new EcosMessage("release(" + objectNumber + ", control)"); 307 tc.sendEcosMessage(m, this); 308 } 309 } 310 311 } 312 313 // Listen for status changes from ECoS system. 314 int newstate = UNKNOWN; 315 int newstateext = UNKNOWN; 316 317 @Override 318 public void reply(EcosReply m) { 319 320 String msg = m.toString(); 321 if (m.getResultCode() != 0) { 322 return; //The result is not valid therefore we can not set it. 323 } 324 if (m.getEcosObjectId() != objectNumber) { 325 return; //message is not for our turnout address 326 } 327 if (msg.contains("switching[0]")) { 328 log.debug("Turnout switched - new state={}", newstate); 329 /*log.debug("see new state "+newstate+" for "+_number);*/ 330 //newCommandedState(newstate); 331 /*Using newKnownState, as any changes made on the ecos do not show 332 up on the panel. Therefore if an ecos route is fired the panel 333 doesn't change to reflect it.*/ 334 if (extended == 0) { 335 newKnownState(newstate); 336 } else { 337 //The masterObjectNumber is used to determine if this the master or slave decoder 338 //address in an extended accessory object on the ecos. 339 if (masterObjectNumber) { 340 newKnownState(newstate); 341 } else { 342 newKnownState(newstateext); 343 } 344 } 345 } 346 if ((m.isUnsolicited()) || (m.getReplyType().equals("get")) || (m.getReplyType().equals("set"))) { 347 //if (msg.startsWith("<REPLY get(" + objectNumber + ",") || msg.startsWith("<EVENT " + objectNumber + ">")) { 348 int start = msg.indexOf("state["); 349 int end = msg.indexOf("]"); 350 if (start > 0 && end > 0) { 351 String val = msg.substring(start + 6, end); 352 // log.debug("Extended - " + extended + " " + objectNumber); 353 if (extended == 0) { 354 if (val.equals("0")) { 355 newstate = CLOSED; 356 } else if (val.equals("1")) { 357 newstate = THROWN; 358 } else { 359 log.warn("val |{}| from {}", val, msg); 360 } 361 log.debug("newstate found: {}", newstate); 362 if (m.getReplyType().equals("set")) { 363 // wait to set the state until ECOS tells us to (by an event with the contents "switching[0]") 364 } else { 365 newKnownState(newstate); 366 } 367 } else { 368 if (extended == THREEWAY) { //Three way Point. 369 if (val.equals("0")) { 370 newstate = CLOSED; 371 newstateext = CLOSED; 372 } else if (val.equals("1")) { 373 newstate = CLOSED; 374 newstateext = THROWN; 375 } else if (val.equals("2")) { 376 newstate = THROWN; 377 newstateext = CLOSED; 378 } 379 } else if (extended == DOUBLESLIP) { //Double Slip 380 if (val.equals("0")) { 381 newstate = CLOSED; 382 newstateext = CLOSED; 383 } else if (val.equals("1")) { 384 newstate = THROWN; 385 newstateext = THROWN; 386 } else if (val.equals("2")) { 387 newstate = THROWN; 388 newstateext = CLOSED; 389 } else if (val.equals("3")) { 390 newstate = CLOSED; 391 newstateext = THROWN; 392 } 393 } 394 if (m.getReplyType().equals("set")) { 395 // wait to set the state until ECoS tells us to (by an event with the contents "switching[0]") 396 } else { 397 if (masterObjectNumber) { 398 newKnownState(newstate); 399 } else { 400 newKnownState(newstateext); 401 } 402 } 403 } 404 } 405 } 406 } 407 408 @Override 409 public void message(EcosMessage m) { 410 // messages are ignored 411 } 412 413 private final static Logger log = LoggerFactory.getLogger(EcosTurnout.class); 414 415}