001package jmri.jmrit.ussctc; 002 003import java.beans.*; 004import java.util.*; 005import javax.annotation.OverridingMethodsMustInvokeSuper; 006import jmri.*; 007import jmri.util.*; 008 009/** 010 * Drive a signal section on a USS CTC panel. 011 * Implements {@link Section} for both the field and CTC machine parts. 012 * <p> 013 * Based on the Signal interface. 014 * <p> 015 * Note that this intentionally does not turn off indicators when the code button 016 * is pressed unless a change has been requested. This is a model-railroad compromise 017 * to speed up the dispatcher's ability to see what's going on. 018 * 019 * @author Bob Jacobsen Copyright (C) 2007, 2017, 2021 020 */ 021public class SignalHeadSection implements Section<CodeGroupThreeBits, CodeGroupThreeBits> { 022 023 /** 024 * Anonymous object only for testing 025 */ 026 SignalHeadSection() { 027 this.station = new Station<CodeGroupThreeBits, CodeGroupThreeBits>("1", null, new CodeButton("IS1","IT1")); 028 } 029 030 static final int DEFAULT_RUN_TIME_LENGTH = 30000; 031 032 /** 033 * Create and configure. 034 * 035 * Accepts user or system names. 036 * 037 * @param rightHeads Set of Signals to release when rightward travel allowed 038 * @param leftHeads Set of Signals to release when leftward travel allowed 039 * @param leftIndicator Turnout name for leftward indicator 040 * @param stopIndicator Turnout name for stop indicator 041 * @param rightIndicator Turnout name for rightward indicator 042 * @param leftInput Sensor name for rightward side of lever on panel 043 * @param rightInput Sensor name for leftward side of lever on panel 044 * @param station Station to which this Section belongs 045 */ 046 public SignalHeadSection(List<String> rightHeads, List<String> leftHeads, 047 String leftIndicator, String stopIndicator, String rightIndicator, 048 String leftInput, String rightInput, 049 Station<CodeGroupThreeBits, CodeGroupThreeBits> station) { 050 051 this.station = station; 052 053 timeMemory = InstanceManager.getDefault(MemoryManager.class).getMemory( 054 Constants.commonNamePrefix+"SIGNALHEADSECTION"+Constants.commonNameSuffix+"TIME"); // NOI18N 055 if (timeMemory == null) { 056 timeMemory = InstanceManager.getDefault(MemoryManager.class).provideMemory( 057 Constants.commonNamePrefix+"SIGNALHEADSECTION"+Constants.commonNameSuffix+"TIME"); // NOI18N 058 timeMemory.setValue(Integer.valueOf(DEFAULT_RUN_TIME_LENGTH)); 059 } 060 061 NamedBeanHandleManager hm = InstanceManager.getDefault(NamedBeanHandleManager.class); 062 TurnoutManager tm = InstanceManager.getDefault(TurnoutManager.class); 063 SensorManager sm = InstanceManager.getDefault(SensorManager.class); 064 SignalHeadManager shm = InstanceManager.getDefault(SignalHeadManager.class); 065 066 hRightHeads = new ArrayDeque<>(); 067 for (String s : rightHeads) { 068 SignalHead sh = shm.getSignalHead(s); 069 if (sh != null) { 070 hRightHeads.add(hm.getNamedBeanHandle(s,sh)); 071 } else { 072 log.debug("Signal {} for SignalHeadSection wasn't found", s); // NOI18N 073 } 074 } 075 076 hLeftHeads = new ArrayDeque<>(); 077 for (String s : leftHeads) { 078 SignalHead sh = shm.getSignalHead(s); 079 if (sh != null) { 080 hLeftHeads.add(hm.getNamedBeanHandle(s,sh)); 081 } else { 082 log.debug("Signal {} for SignalHeadSection wasn't found", s); // NOI18N 083 } 084 } 085 086 timeLogSensor = InstanceManager.getDefault(SensorManager.class).provideSensor("IS"+Constants.commonNamePrefix 087 +"SIGNALSECTION:"+station.getName()+":RUNNINGTIME" 088 +Constants.commonNameSuffix); 089 timeLogSensor.setCommandedState(Sensor.INACTIVE); 090 091 hLeftIndicator = hm.getNamedBeanHandle(leftIndicator, tm.provideTurnout(leftIndicator)); 092 hStopIndicator = hm.getNamedBeanHandle(stopIndicator, tm.provideTurnout(stopIndicator)); 093 hRightIndicator = hm.getNamedBeanHandle(rightIndicator, tm.provideTurnout(rightIndicator)); 094 095 hLeftInput = hm.getNamedBeanHandle(leftInput, sm.provideSensor(leftInput)); 096 hRightInput = hm.getNamedBeanHandle(rightInput, sm.provideSensor(rightInput)); 097 098 // initialize lamps to follow layout state to STOP 099 tm.provideTurnout(leftIndicator).setCommandedState(Turnout.CLOSED); 100 tm.provideTurnout(stopIndicator).setCommandedState(Turnout.THROWN); 101 tm.provideTurnout(rightIndicator).setCommandedState(Turnout.CLOSED); 102 // hold everything 103 setListHeldState(hRightHeads, true); 104 setListHeldState(hLeftHeads, true); 105 106 // add listeners 107 for (NamedBeanHandle<Signal> b : hRightHeads) { 108 b.getBean().addPropertyChangeListener( 109 (java.beans.PropertyChangeEvent e) -> { 110 jmri.util.ThreadingUtil.runOnLayoutEventually( ()->{ 111 layoutSignalHeadChanged(e); 112 }); 113 } 114 ); 115 } 116 for (NamedBeanHandle<Signal> b : hLeftHeads) { 117 b.getBean().addPropertyChangeListener( 118 (java.beans.PropertyChangeEvent e) -> { 119 jmri.util.ThreadingUtil.runOnLayoutEventually( ()->{ 120 layoutSignalHeadChanged(e); 121 }); 122 } 123 ); 124 } 125 } 126 127 Memory timeMemory = null; 128 129 Sensor timeLogSensor; 130 131 ArrayDeque<NamedBeanHandle<Signal>> hRightHeads; 132 ArrayDeque<NamedBeanHandle<Signal>> hLeftHeads; 133 134 NamedBeanHandle<Turnout> hLeftIndicator; 135 NamedBeanHandle<Turnout> hStopIndicator; 136 NamedBeanHandle<Turnout> hRightIndicator; 137 138 NamedBeanHandle<Sensor> hLeftInput; 139 NamedBeanHandle<Sensor> hRightInput; 140 141 // coding used locally to ensure consistency 142 public static final CodeGroupThreeBits CODE_LEFT = CodeGroupThreeBits.Triple100; 143 public static final CodeGroupThreeBits CODE_STOP = CodeGroupThreeBits.Triple010; 144 public static final CodeGroupThreeBits CODE_RIGHT = CodeGroupThreeBits.Triple001; 145 public static final CodeGroupThreeBits CODE_OFF = CodeGroupThreeBits.Triple000; 146 147 // States to track changes at the Code Machine end 148 enum Machine { 149 SET_LEFT, 150 SET_STOP, 151 SET_RIGHT 152 } 153 Machine machine = Machine.SET_STOP; 154 155 CodeGroupThreeBits lastIndication = CODE_STOP; 156 void setLastIndication(CodeGroupThreeBits v) { 157 log.trace("lastIndication goes from {} to {}", lastIndication, v); 158 CodeGroupThreeBits old = lastIndication; 159 lastIndication = v; 160 firePropertyChange("LastIndication", old, lastIndication); // NOI18N 161 } 162 CodeGroupThreeBits getLastIndication() { return lastIndication; } 163 164 boolean timeRunning = false; 165 166 public boolean isRunningTime() { return timeRunning; } 167 168 Station<CodeGroupThreeBits, CodeGroupThreeBits> station; 169 @Override 170 public Station<CodeGroupThreeBits, CodeGroupThreeBits> getStation() { return station;} 171 @Override 172 public String getName() { return "SH for "+hStopIndicator.getBean().getDisplayName(); } 173 174 List<Lock> rightwardLocks; 175 List<Lock> leftwardLocks; 176 public void addRightwardLocks(List<Lock> locks) { this.rightwardLocks = locks; } 177 public void addLeftwardLocks(List<Lock> locks) { this.leftwardLocks = locks; } 178 179 /** 180 * Start of sending code operation: 181 * <ul> 182 * <li>Set indicators off if a change has been requested 183 * <li>Provide values to send over line 184 * </ul> 185 * @return code line value to transmit from machine to field 186 */ 187 @Override 188 public CodeGroupThreeBits codeSendStart() { 189 // are we setting to stop, which might start running time? 190 // check for setting to stop while machine has been cleared to left or right 191 if ( (hRightInput.getBean().getKnownState()==Sensor.ACTIVE && 192 hLeftIndicator.getBean().getKnownState() == Turnout.THROWN ) 193 || 194 (hLeftInput.getBean().getKnownState()==Sensor.ACTIVE && 195 hRightIndicator.getBean().getKnownState() == Turnout.THROWN ) 196 || 197 (hLeftInput.getBean().getKnownState()!=Sensor.ACTIVE && hRightInput.getBean().getKnownState()!=Sensor.ACTIVE && 198 ( hRightIndicator.getBean().getKnownState() == Turnout.THROWN || hLeftIndicator.getBean().getKnownState() == Turnout.THROWN) ) 199 ) { 200 201 // setting to stop, have to start running time 202 startRunningTime(); 203 } 204 205 // Set the indicators based on current and requested state 206 if ( !timeRunning && ( 207 ( machine==Machine.SET_LEFT && hLeftInput.getBean().getKnownState()==Sensor.ACTIVE) 208 || ( machine==Machine.SET_RIGHT && hRightInput.getBean().getKnownState()==Sensor.ACTIVE) 209 || ( machine==Machine.SET_STOP && hRightInput.getBean().getKnownState()!=Sensor.ACTIVE && hLeftInput.getBean().getKnownState()!=Sensor.ACTIVE) ) 210 ) { 211 log.debug("No signal change required, states aligned"); // NOI18N 212 } else { 213 log.debug("Signal change requested"); // NOI18N 214 // have to turn off 215 hLeftIndicator.getBean().setCommandedState(Turnout.CLOSED); 216 hStopIndicator.getBean().setCommandedState(Turnout.CLOSED); 217 hRightIndicator.getBean().setCommandedState(Turnout.CLOSED); 218 } 219 220 // return the settings to send 221 CodeGroupThreeBits retval; 222 if (timeRunning) { 223 machine = Machine.SET_STOP; 224 retval = CODE_STOP; 225 } else if (hLeftInput.getBean().getKnownState()==Sensor.ACTIVE) { 226 machine = Machine.SET_LEFT; 227 retval = CODE_LEFT; 228 } else if (hRightInput.getBean().getKnownState()==Sensor.ACTIVE) { 229 machine = Machine.SET_RIGHT; 230 retval = CODE_RIGHT; 231 } else { 232 machine = Machine.SET_STOP; 233 retval = CODE_STOP; 234 } 235 log.debug("codeSendStart returns {}", retval); 236 237 // A model thought - if setting stop, hold signals immediately 238 // instead of waiting for code cycle. Model railroads move fast... 239 //if (retval == CODE_STOP) { 240 // setListHeldState(hRightHeads, true); 241 // setListHeldState(hLeftHeads, true); 242 //} 243 244 return retval; 245 } 246 247 void startRunningTime() { 248 if (timeRunning) { 249 log.error("Attempt to start running time while it is already running", 250 LoggingUtil.shortenStacktrace(new Exception("traceback"))); 251 return; 252 } 253 timeRunning = true; 254 timeLogSensor.setCommandedState(Sensor.ACTIVE); 255 jmri.util.ThreadingUtil.runOnLayoutDelayed( ()->{ 256 log.debug("End running time: Station {}", station.getName()); // NOI18N 257 Lock.signalLockLogger.setStatus(this, "End running time: Station "+station.getName()); 258 timeLogSensor.setCommandedState(Sensor.INACTIVE); 259 if (!timeRunning) log.warn("Running time timer ended while not marked as running time"); 260 timeRunning = false; 261 station.requestIndicationStart(); 262 } , 263 (int)timeMemory.getValue()); 264 265 Lock.signalLockLogger.setStatus(this, "Running time: Station "+station.getName()); 266 } 267 268 public static int MOVEMENT_DELAY = 5000; 269 270 boolean deferIndication = false; // when set, don't indicate on layout change 271 // because something else will ensure it later 272 273 /** 274 * Code arrives in field. Sets the signals on the layout. 275 */ 276 @Override 277 public void codeValueDelivered(CodeGroupThreeBits value) { 278 // Set signals. While doing that, remember command as indication, so that the 279 // following signal change won't drive an _immediate_ indication cycle. 280 // Also, always go via stop... 281 CodeGroupThreeBits currentIndication = getCurrentIndication(); 282 log.debug("codeValueDelivered sets value {} current: {} last: {}", value, currentIndication, lastIndication); 283 284 if (value == CODE_LEFT && Lock.checkLocksClear(leftwardLocks, Lock.signalLockLogger)) { 285 // setLastIndication(CODE_STOP); 286 // setListHeldState(hRightHeads, true); 287 // setListHeldState(hLeftHeads, true); 288 setLastIndication(CODE_LEFT); 289 log.debug("Layout signals set LEFT"); // NOI18N 290 setListHeldState(hLeftHeads, false); 291 setListHeldState(hRightHeads, true); 292 } else if (value == CODE_RIGHT && Lock.checkLocksClear(rightwardLocks, Lock.signalLockLogger)) { 293 // lastIndication = CODE_STOP; 294 // setListHeldState(hRightHeads, true); 295 // setListHeldState(hLeftHeads, true); 296 setLastIndication(CODE_RIGHT); 297 log.debug("Layout signals set RIGHT"); // NOI18N 298 setListHeldState(hRightHeads, false); 299 setListHeldState(hLeftHeads, true); 300 } else if (value == CODE_STOP) { 301 setLastIndication(CODE_STOP); 302 log.debug("Layout signals set STOP"); // NOI18N 303 setListHeldState(hRightHeads, true); 304 setListHeldState(hLeftHeads, true); 305 } else { 306 // RIGHT or LEFT but locks not clear 307 Lock.signalLockLogger.setStatus(this, 308 "Force stop: left clear "+Lock.checkLocksClear(leftwardLocks, Lock.signalLockLogger) 309 +", right clear "+Lock.checkLocksClear(rightwardLocks, Lock.signalLockLogger)); 310 setLastIndication(CODE_STOP); 311 log.debug("Layout signals set STOP due to locks"); // NOI18N 312 setListHeldState(hRightHeads, true); 313 setListHeldState(hLeftHeads, true); 314 } 315 316 // start the timer for the signals to change 317 if (currentIndication != lastIndication) { 318 log.debug("codeValueDelivered started timer for return indication"); // NOI18N 319 jmri.util.TimerUtil.schedule(new TimerTask() { // NOI18N 320 @Override 321 public void run() { 322 jmri.util.ThreadingUtil.runOnLayout( ()->{ 323 log.debug("end of movement delay from codeValueDelivered"); 324 station.requestIndicationStart(); 325 } ); 326 } 327 }, MOVEMENT_DELAY); 328 } 329 log.debug("End codeValueDelivered"); 330 } 331 332 protected void setListHeldState(Iterable<NamedBeanHandle<Signal>> list, boolean state) { 333 for (NamedBeanHandle<Signal> handle : list) { 334 if (handle.getBean().getHeld() != state) handle.getBean().setHeld(state); 335 } 336 } 337 338 @Override 339 public String toString() { 340 StringBuffer retVal = new StringBuffer("SignalHeadSection "); // NOI18N 341 342 retVal.append(" state: "+machine); // NOI18N 343 retVal.append(" time: "+isRunningTime()); // NOI18N 344 retVal.append(" defer: "+deferIndication); // NOI18N 345 346 for (NamedBeanHandle<Signal> handle : hRightHeads) { 347 retVal.append("\n \"").append(handle.getName()).append("\" "); // NOI18N 348 retVal.append(" held: "+handle.getBean().getHeld()+" "); // NOI18N 349 retVal.append(" clear: "+handle.getBean().isCleared()+" "); // NOI18N 350 retVal.append(" stop: "+handle.getBean().isAtStop()); // NOI18N 351 } 352 for (NamedBeanHandle<Signal> handle : hLeftHeads) { 353 retVal.append("\n \"").append(handle.getName()).append("\" "); // NOI18N 354 retVal.append(" held: "+handle.getBean().getHeld()+" "); // NOI18N 355 retVal.append(" clear: "+handle.getBean().isCleared()+" "); // NOI18N 356 retVal.append(" stop: "+handle.getBean().isAtStop()); // NOI18N 357 } 358 359 return retVal.toString(); 360 } 361 362 /** 363 * Provide state that's returned from field to machine via indication. 364 */ 365 @Override 366 public CodeGroupThreeBits indicationStart() { 367 // check for signal drop unexpectedly, other automatic clears 368 // is done in getCurrentIndication() 369 CodeGroupThreeBits retval = getCurrentIndication(); 370 log.debug("indicationStart with {}; last indication was {}", retval, lastIndication); // NOI18N 371 372 // TODO: anti-fleeting done always, need call-on logic 373 374 375 setLastIndication(retval); 376 return retval; 377 } 378 379 /** 380 * Clear is defined as showing above Restricting. 381 * We implement that as not Held, not RED, not Restricting. 382 * @param handle signal bean handle. 383 * @return true if clear. 384 */ 385 public boolean headShowsClear(NamedBeanHandle<Signal> handle) { 386 return !handle.getBean().getHeld() && handle.getBean().isCleared(); 387 } 388 389 /** 390 * "Restricting" means that a signal is showing FLASHRED 391 * @param handle signal bean handle. 392 * @return true if showing restricting. 393 */ 394 public boolean headShowsRestricting(NamedBeanHandle<Signal> handle) { 395 return handle.getBean().isShowingRestricting(); 396 } 397 398 /** 399 * Work out current indication from layout status. 400 * @return code group. 401 */ 402 public CodeGroupThreeBits getCurrentIndication() { 403 log.trace("Start getCurrentIndication with lastIndication {}", lastIndication, LoggingUtil.shortenStacktrace(new Exception("traceback"))); 404 boolean leftClear = false; 405 boolean leftHeld = false; 406 boolean leftRestricting = false; 407 for (NamedBeanHandle<Signal> handle : hLeftHeads) { 408 if (headShowsClear(handle)) leftClear = true; 409 if (handle.getBean().getHeld()) leftHeld = true; 410 if (headShowsRestricting(handle)) leftRestricting = true; 411 } 412 boolean rightClear = false; 413 boolean rightHeld = false; 414 boolean rightRestricting = false; 415 for (NamedBeanHandle<Signal> handle : hRightHeads) { 416 if (headShowsClear(handle)) rightClear = true; 417 if (handle.getBean().getHeld()) rightHeld = true; 418 if (headShowsRestricting(handle)) rightRestricting = true; 419 } 420 log.trace(" found leftClear {}, leftHeld {}, leftRestricting {}, lastIndication {}", leftClear, leftHeld, leftRestricting, lastIndication); // NOI18N 421 log.trace(" rightClear {}, rightHeld {}, rightRestricting {}", rightClear, rightHeld, rightRestricting); // NOI18N 422 if (leftClear && rightClear) log.error("Found both left and right clear: {}", this); // NOI18N 423 if (leftClear && rightRestricting) log.warn("Found left clear and right at restricting: {}", this); // NOI18N 424 if (leftRestricting && rightClear) log.warn("Found left at restricting and right clear: {}", this); // NOI18N 425 426 427 CodeGroupThreeBits retval; 428 429 // Restricting cases show OFF 430 if (leftRestricting || rightRestricting) { 431 Lock.signalLockLogger.setStatus(this, "Force off due to restricting"); 432 retval = CODE_OFF; 433 } else if ((!leftClear) && (!rightClear)) { 434 // check for a signal dropping while cleared 435 if (lastIndication != CODE_STOP) { 436 log.debug("CurrentIndication stop due to right and left not clear with {}", lastIndication); 437 Lock.signalLockLogger.setStatus(this, "Show stop due to right and left not clear"); 438 } else { 439 Lock.signalLockLogger.clear(); 440 } 441 retval = CODE_STOP; 442 } else if ((!leftClear) && rightClear && (lastIndication == CODE_RIGHT )) { 443 Lock.signalLockLogger.clear(); 444 retval = CODE_RIGHT; 445 } else if (leftClear && (!rightClear) && (lastIndication == CODE_LEFT)) { 446 Lock.signalLockLogger.clear(); 447 retval = CODE_LEFT; 448 } else { 449 log.debug("Not individually cleared, set OFF"); 450 if (!rightClear) Lock.signalLockLogger.setStatus(this, "Force stop due to right not clear"); 451 else if (!leftClear) Lock.signalLockLogger.setStatus(this, "Force stop due to left not clear"); 452 else Lock.signalLockLogger.setStatus(this, "Force stop due to vital settings"); 453 retval = CODE_OFF; 454 } 455 log.trace("End getCurrentIndication returns {}", retval); 456 return retval; 457 } 458 459 /** 460 * Process values received from the field unit. 461 */ 462 @Override 463 public void indicationComplete(CodeGroupThreeBits value) { 464 log.debug("indicationComplete sets from {} in state {}", value, machine); // NOI18N 465 if (timeRunning) { 466 hLeftIndicator.getBean().setCommandedState(Turnout.CLOSED); 467 hStopIndicator.getBean().setCommandedState(Turnout.CLOSED); 468 hRightIndicator.getBean().setCommandedState(Turnout.CLOSED); 469 } else switch (value) { 470 case Triple100: // CODE_LEFT 471 hLeftIndicator.getBean().setCommandedState(Turnout.THROWN); 472 hStopIndicator.getBean().setCommandedState(Turnout.CLOSED); 473 hRightIndicator.getBean().setCommandedState(Turnout.CLOSED); 474 break; 475 case Triple010: // CODE_STOP 476 hLeftIndicator.getBean().setCommandedState(Turnout.CLOSED); 477 hStopIndicator.getBean().setCommandedState(Turnout.THROWN); 478 hRightIndicator.getBean().setCommandedState(Turnout.CLOSED); 479 break; 480 case Triple001: // CODE_RIGHT 481 hLeftIndicator.getBean().setCommandedState(Turnout.CLOSED); 482 hStopIndicator.getBean().setCommandedState(Turnout.CLOSED); 483 hRightIndicator.getBean().setCommandedState(Turnout.THROWN); 484 break; 485 case Triple000: // CODE_OFF 486 hLeftIndicator.getBean().setCommandedState(Turnout.CLOSED); // all off 487 hStopIndicator.getBean().setCommandedState(Turnout.CLOSED); 488 hRightIndicator.getBean().setCommandedState(Turnout.CLOSED); 489 break; 490 default: 491 log.error("Got code not recognized: {}", value); 492 hLeftIndicator.getBean().setCommandedState(Turnout.CLOSED); 493 hStopIndicator.getBean().setCommandedState(Turnout.CLOSED); 494 hRightIndicator.getBean().setCommandedState(Turnout.CLOSED); 495 break; 496 } 497 } 498 499 void layoutSignalHeadChanged(java.beans.PropertyChangeEvent e) { 500 CodeGroupThreeBits current = getCurrentIndication(); 501 // as a modeling thought, if we're dropping to stop, set held right now 502 log.debug("layoutSignalHeadChanged with last: {} current: {} defer: {}, driving update", lastIndication, current, deferIndication); 503 if (current == CODE_STOP && current != lastIndication && ! deferIndication ) { 504 deferIndication = true; 505 setListHeldState(hRightHeads, true); 506 setListHeldState(hLeftHeads, true); 507 deferIndication = false; 508 } 509 510 // if there was a change, need to send indication back to central 511 if (current != lastIndication && ! deferIndication) { 512 log.debug(" SignalHead change sends changed Indication last: {} current: {} defer: {}, driving update", lastIndication, current, deferIndication); 513 station.requestIndicationStart(); 514 } else { 515 log.debug(" SignalHead change without change in Indication"); 516 } 517 log.debug("end of layoutSignalHeadChanged"); 518 } 519 520 final PropertyChangeSupport pcs = new PropertyChangeSupport(this); 521 522 @OverridingMethodsMustInvokeSuper 523 public synchronized void addPropertyChangeListener(PropertyChangeListener l) { 524 pcs.addPropertyChangeListener(l); 525 } 526 527 @OverridingMethodsMustInvokeSuper 528 public synchronized void removePropertyChangeListener(PropertyChangeListener l) { 529 pcs.removePropertyChangeListener(l); 530 } 531 532 @OverridingMethodsMustInvokeSuper 533 protected void firePropertyChange(String p, Object old, Object n) { 534 pcs.firePropertyChange(p, old, n); 535 } 536 537 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SignalHeadSection.class); 538}