001package jmri.jmrit.ctc; 002import java.beans.PropertyChangeEvent; 003import java.beans.PropertyChangeListener; 004import java.util.HashSet; 005import jmri.*; 006import org.slf4j.LoggerFactory; 007 008/** 009 * This is the "master" class that handles everything when a code button is 010 * pressed. As such, it has a LOT of external data passed into it's constructor, 011 * and operates and modifies all objects it contains on a dynamic basis both 012 * when the button is pressed, and when external events happen that affect this 013 * object. 014 * <p> 015 * Notes: 016 * <p> 017 * Changing both signal direction to non signals normal and switch direction at the same time "is allowed". 018 * Lock/Unlock is the LOWEST priority! Call on is the HIGHEST priority. 019 * <p> 020 * As of V1.04 of the CTC system, preconditioning (a.k.a. stacking) is supported. It is enabled 021 * by setting the internal sensor (automatically created) "IS:PRECONDITIONING_ENABLED" to active. 022 * Any other value inactivates this feature. For example, the user can create a toggle 023 * switch to activate / inactivate it. 024 * 025 * @author Gregory J. Bedlek Copyright (C) 2018, 2019, 2020 026 */ 027public class CodeButtonHandler { 028 private final boolean _mTurnoutLockingOnlyEnabled; 029 private final LockedRoutesManager _mLockedRoutesManager; 030 private final String _mUserIdentifier; 031 private final int _mUniqueID; 032 private final NBHSensor _mCodeButtonInternalSensor; 033 private final PropertyChangeListener _mCodeButtonInternalSensorPropertyChangeListener; 034 private final NBHSensor _mOSSectionOccupiedExternalSensor; 035 private final NBHSensor _mOSSectionOccupiedExternalSensor2; 036 private final PropertyChangeListener _mOSSectionOccupiedExternalSensorPropertyChangeListener; 037 private final SignalDirectionIndicatorsInterface _mSignalDirectionIndicators; 038 private final SignalDirectionLever _mSignalDirectionLever; 039 private final SwitchDirectionIndicators _mSwitchDirectionIndicators; 040 private final SwitchDirectionLever _mSwitchDirectionLever; 041 private final Fleeting _mFleeting; 042 private final CallOn _mCallOn; 043 private final TrafficLocking _mTrafficLocking; 044 private final TurnoutLock _mTurnoutLock; 045 private final IndicationLockingSignals _mIndicationLockingSignals; 046 private final CodeButtonSimulator _mCodeButtonSimulator; 047 private LockedRoute _mLockedRoute = null; 048 049 private static final Sensor _mPreconditioningEnabledSensor = initializePreconditioningEnabledSensor(); 050 private static class PreconditioningData { 051 public boolean _mCodeButtonPressed = false; // If false, values in these don't matter: 052 public int _mSignalDirectionLeverWas = CTCConstants.OUTOFCORRESPONDENCE; // Safety: 053 public int _mSwitchDirectionLeverWas = CTCConstants.OUTOFCORRESPONDENCE; 054 } 055 056 private static Sensor initializePreconditioningEnabledSensor() { 057 Sensor returnValue = InstanceManager.sensorManagerInstance().newSensor("IS:PRECONDITIONING_ENABLED", null); // NOI18N 058 int knownState = returnValue.getKnownState(); 059 if (Sensor.ACTIVE != knownState && Sensor.INACTIVE != knownState) { 060 try {returnValue.setKnownState(Sensor.INACTIVE); } catch (JmriException ex) { 061 LoggerFactory.getLogger(CodeButtonHandler.class).debug("Sensor problem, preconditioning won't work."); // NOI18N 062 } 063 } 064 return returnValue; 065 } 066 private PreconditioningData _mPreconditioningData = new PreconditioningData(); 067 068 public CodeButtonHandler( boolean turnoutLockingOnlyEnabled, // If this is NOT an O.S. section, but only a turnout lock, then this is true. 069 LockedRoutesManager lockedRoutesManager, 070 String userIdentifier, 071 int uniqueID, 072 NBHSensor codeButtonInternalSensor, // Required 073 int codeButtonDelayInMilliseconds, // If 0, REAL code button, if > 0, tower operations (simulated code button). 074 NBHSensor osSectionOccupiedExternalSensor, // Required, if ACTIVE prevents turnout, lock or call on from occuring. 075 NBHSensor osSectionOccupiedExternalSensor2, // Optional, if ACTIVE prevents turnout, lock or call on from occuring. 076 SignalDirectionIndicatorsInterface signalDirectionIndicators, // Required 077 SignalDirectionLever signalDirectionLever, 078 SwitchDirectionIndicators switchDirectionIndicators, 079 SwitchDirectionLever switchDirectionLever, 080 Fleeting fleeting, // If null, then ALWAYS fleeting! 081 CallOn callOn, 082 TrafficLocking trafficLocking, 083 TurnoutLock turnoutLock, 084 IndicationLockingSignals indicationLockingSignals) { // Needed for check of adjacent OS Section(s), and optionally turnoutLock. 085 signalDirectionIndicators.setCodeButtonHandler(this); 086 _mTurnoutLockingOnlyEnabled = turnoutLockingOnlyEnabled; 087 _mLockedRoutesManager = lockedRoutesManager; 088 _mUserIdentifier = userIdentifier; 089 _mUniqueID = uniqueID; 090 _mSignalDirectionIndicators = signalDirectionIndicators; 091 _mSignalDirectionLever = signalDirectionLever; 092 _mSwitchDirectionIndicators = switchDirectionIndicators; 093 _mSwitchDirectionLever = switchDirectionLever; 094 _mFleeting = fleeting; 095 _mCallOn = callOn; 096 _mTrafficLocking = trafficLocking; 097 _mTurnoutLock = turnoutLock; 098 _mIndicationLockingSignals = indicationLockingSignals; 099 _mCodeButtonInternalSensor = codeButtonInternalSensor; 100 _mCodeButtonInternalSensor.setKnownState(Sensor.INACTIVE); 101 _mCodeButtonInternalSensorPropertyChangeListener = (PropertyChangeEvent e) -> { codeButtonStateChange(e); }; 102 _mCodeButtonInternalSensor.addPropertyChangeListener(_mCodeButtonInternalSensorPropertyChangeListener); 103 104 _mOSSectionOccupiedExternalSensorPropertyChangeListener = (PropertyChangeEvent e) -> { osSectionPropertyChangeEvent(e); }; 105 _mOSSectionOccupiedExternalSensor = osSectionOccupiedExternalSensor; 106 _mOSSectionOccupiedExternalSensor.addPropertyChangeListener(_mOSSectionOccupiedExternalSensorPropertyChangeListener); 107 108// NO property change for this, only used for turnout locking: 109 _mOSSectionOccupiedExternalSensor2 = osSectionOccupiedExternalSensor2; 110 111 if (codeButtonDelayInMilliseconds > 0) { // SIMULATED code button: 112 _mCodeButtonSimulator = new CodeButtonSimulator(codeButtonDelayInMilliseconds, 113 _mCodeButtonInternalSensor, 114 _mSwitchDirectionLever, 115 _mSignalDirectionLever, 116 _mTurnoutLock); 117 } else { 118 _mCodeButtonSimulator = null; 119 } 120 } 121 122 /** 123 * This routine SHOULD ONLY be called by CTCMain when the CTC system is shutdown 124 * in order to clean up all resources prior to a restart. Nothing else should 125 * call this. 126 */ 127 public void removeAllListeners() { 128// Remove our registered listeners first: 129 _mCodeButtonInternalSensor.removePropertyChangeListener(_mCodeButtonInternalSensorPropertyChangeListener); 130 _mOSSectionOccupiedExternalSensor.removePropertyChangeListener(_mOSSectionOccupiedExternalSensorPropertyChangeListener); 131// Give each object a chance to remove theirs also: 132 if (_mSignalDirectionIndicators != null) _mSignalDirectionIndicators.removeAllListeners(); 133 if (_mSignalDirectionLever != null) _mSignalDirectionLever.removeAllListeners(); 134 if (_mSwitchDirectionIndicators != null) _mSwitchDirectionIndicators.removeAllListeners(); 135 if (_mSwitchDirectionLever != null) _mSwitchDirectionLever.removeAllListeners(); 136 if (_mFleeting != null) _mFleeting.removeAllListeners(); 137 if (_mCallOn != null) _mCallOn.removeAllListeners(); 138 if (_mTrafficLocking != null) _mTrafficLocking.removeAllListeners(); 139 if (_mTurnoutLock != null) _mTurnoutLock.removeAllListeners(); 140 if (_mIndicationLockingSignals != null) _mIndicationLockingSignals.removeAllListeners(); 141 if (_mCodeButtonSimulator != null) _mCodeButtonSimulator.removeAllListeners(); 142 } 143 144 /** 145 * SignalDirectionIndicators calls us here when time locking is done. 146 */ 147 public void cancelLockedRoute() { 148 _mLockedRoutesManager.cancelLockedRoute(_mLockedRoute); // checks passed parameter for null for us 149 _mLockedRoute = null; // Not valid anymore. 150 } 151 152 public boolean uniqueIDMatches(int uniqueID) { return _mUniqueID == uniqueID; } 153 public NBHSensor getOSSectionOccupiedExternalSensor() { return _mOSSectionOccupiedExternalSensor; } 154 155 private void osSectionPropertyChangeEvent(PropertyChangeEvent e) { 156 if (isPrimaryOSSectionOccupied()) { // MUST ALWAYS process PRIMARY OS occupied state change to ACTIVE (It's the only one that comes here anyways!) 157 if (_mFleeting != null && !_mFleeting.isFleetingEnabled()) { // Impliment "stick" here: 158 _mSignalDirectionIndicators.forceAllSignalsToHeld(); 159 } 160 _mSignalDirectionIndicators.osSectionBecameOccupied(); 161 } 162 else { // Process pre-conditioning if available: 163 if (_mPreconditioningData._mCodeButtonPressed) { 164 doCodeButtonPress(); 165 _mPreconditioningData._mCodeButtonPressed = false; 166 } 167 } 168 } 169 170 public void externalLockTurnout() { 171 if (_mTurnoutLock != null) _mTurnoutLock.externalLockTurnout(); 172 } 173 174 private void codeButtonStateChange(PropertyChangeEvent e) { 175 if (e.getPropertyName().equals("KnownState") && (int)e.getNewValue() == Sensor.ACTIVE) { 176 177/* NO MATTER what else happens, IF the dispatcher has Signals Indicator Normal lit, sets the Signal Direction Lever to Normal, 178 and has pressed the Code Button, we DELETE any active routes allocated to this OS section FIRST, ALWAYS without question! 179 180Why is this needed? Many reasons, a few of which are iterated below: 181 182#1) It is possible for another train to accidentally enter the O.S. section and drop the signals 183 in front of the actual train that was supposed to transit the O.S. section. Then the "bad" train backs out, 184 and the dispatcher is now unable to clear the route since the signals are already red from that "transgression", 185 and the route is still allocated, "minus" that single transgressed section. 186 187#2) Hardware can "fail". For instance, if the hardware does not send the "unocciped" message to JMRI, 188 then that route will not be available "forever" due to my not purging that internally allocated sensor. 189 190#3) On a BUSY JMRI Loconet system (or other system), the hardware sends the message, but the message is permanently 191 lost. Ditto problem. 192*/ 193 if (_mSignalDirectionIndicators.signalsNormal() && getCurrentSignalDirectionLever(false) == CTCConstants.SIGNALSNORMAL) { 194 cancelLockedRoute(); 195 } 196 197// NOTE: If the primary O.S. section is occupied, you CANT DO ANYTHING via a CTC machine, except: 198// Preconditioning: IF the O.S. section is occupied, then it is a pre-conditioning request: 199 if (isPrimaryOSSectionOccupied()) { 200 if (Sensor.ACTIVE == _mPreconditioningEnabledSensor.getKnownState()) { // ONLY if turned on: 201 _mPreconditioningData._mSignalDirectionLeverWas = getCurrentSignalDirectionLever(false); 202 _mPreconditioningData._mSwitchDirectionLeverWas = getSwitchDirectionLeverRequestedState(false); 203 _mPreconditioningData._mCodeButtonPressed = true; // Do this LAST so that the above variables are stable in this object, 204 // in case there is a multi-threading issue (yea, lock it would be better, 205 // but this is good enough for now!) 206 } 207 } 208 doCodeButtonPress(); 209 } 210 } 211 212 private void doCodeButtonPress() { 213 if (_mSignalDirectionIndicators.isRunningTime()) return; // If we are running time, IGNORE all requests from the user: 214 possiblyAllowLockChange(); // MUST unlock first, otherwise if dispatcher wanted to unlock and change switch state, it wouldn't! 215 possiblyAllowTurnoutChange(); // Change turnout 216// IF the call on was accepted, then we DON'T attempt to change the signals to a more favorable 217// aspect here. Additionally see the comments above CallOn.java/"codeButtonPressed" for an explanation 218// of a "fake out" that happens in that routine, and it's effect on this code here: 219 if (!possiblyAllowCallOn()) { // NO call on occured or was allowed or requested: 220 possiblyAllowSignalDirectionChange(); // Slave to it! 221 } 222 } 223 224// Returns true if call on was actually done, else false 225 private boolean possiblyAllowCallOn() { 226 boolean returnStatus = false; 227 if (allowCallOnChange()) { 228 HashSet<Sensor> sensors = new HashSet<>(); // Initial O.S. section sensor(s): 229 sensors.add(_mOSSectionOccupiedExternalSensor.getBean()); // Always. 230// If there is a switch direction indicator, and it is reversed, then add the other sensor if valid here: 231 if (_mSwitchDirectionIndicators != null && _mSwitchDirectionIndicators.getLastIndicatorState() == CTCConstants.SWITCHREVERSED) { 232 if (_mOSSectionOccupiedExternalSensor2.valid()) sensors.add(_mOSSectionOccupiedExternalSensor2.getBean()); 233 } 234// NOTE: We DO NOT support preconditioning of call on, ergo false passed to "getCurrentSignalDirectionLever" 235 TrafficLockingInfo trafficLockingInfo = _mCallOn.codeButtonPressed(sensors, _mUserIdentifier, _mSignalDirectionIndicators, getCurrentSignalDirectionLever(false)); 236 if (trafficLockingInfo._mLockedRoute != null) { // Was allocated: 237 _mLockedRoute = trafficLockingInfo._mLockedRoute; 238 } 239 returnStatus = trafficLockingInfo._mReturnStatus; 240 } 241 if (_mCallOn != null) _mCallOn.resetToggle(); 242 return returnStatus; 243 } 244 245/* 246Rules from http://www.ctcparts.com/about.htm 247 "An important note though for programming logic is that the interlocking limits 248must be clear and all power switches within the interlocking limits aligned 249appropriately for the back to train route for this feature to activate." 250*/ 251 private boolean allowCallOnChange() { 252// Safety checks: 253 if (_mCallOn == null) return false; 254// Rules: 255 if (isPrimaryOSSectionOccupied()) return false; 256 if (_mSignalDirectionIndicators.isRunningTime()) return false; 257 if (_mSignalDirectionIndicators.getSignalsInTheFieldDirection() != CTCConstants.SIGNALSNORMAL) return false; 258 if (!areOSSensorsAvailableInRoutes()) return false; 259 return true; 260 } 261 262// If it doesn't exist, this returns OUTOFCORRESPONDENCE, else return it's present state: 263// NOTE: IF a preconditioned input was available, it OVERRIDES actual Signal Direction Lever (which is ignored in this case). 264 private int getCurrentSignalDirectionLever(boolean allowMergeInPreconditioning) { 265 if (_mSignalDirectionLever == null) return CTCConstants.OUTOFCORRESPONDENCE; 266 if (allowMergeInPreconditioning && _mPreconditioningData._mCodeButtonPressed) { // We can check and it is available: 267 if (_mPreconditioningData._mSignalDirectionLeverWas == CTCConstants.LEFTTRAFFIC 268 || _mPreconditioningData._mSignalDirectionLeverWas == CTCConstants.RIGHTTRAFFIC) { // Was valid: 269 return _mPreconditioningData._mSignalDirectionLeverWas; 270 } 271 } 272 return _mSignalDirectionLever.getPresentSignalDirectionLeverState(); 273 } 274 275 private void possiblyAllowTurnoutChange() { 276 if (allowTurnoutChange()) { 277 int requestedState = getSwitchDirectionLeverRequestedState(true); 278 notifyTurnoutLockObjectOfNewAlignment(requestedState); // Tell lock object this is new alignment 279 if (_mSwitchDirectionIndicators != null) { // Safety: 280 _mSwitchDirectionIndicators.codeButtonPressed(requestedState); // Also sends commmands to move the points 281 } 282 } 283 } 284 285 private boolean allowTurnoutChange() { 286// Safety checks: 287// Rules: 288 if (!_mSignalDirectionIndicators.signalsNormal()) return false; 289 if (routeClearedAcross()) return false; // Something was cleared thru, NO CHANGE 290 if (isEitherOSSectionOccupied()) return false; 291// 6/28/16: If the switch direction indicators are presently "OUTOFCORRESPONDENCE", IGNORE request, as we are presently working on a change: 292 if (!switchDirectionIndicatorsInCorrespondence()) return false; 293 if (!turnoutPresentlyLocked()) return false; 294 if (!areOSSensorsAvailableInRoutes()) return false; 295 return true; 296 } 297 298 private void notifyTurnoutLockObjectOfNewAlignment(int requestedState) { 299 if (_mTurnoutLock != null) _mTurnoutLock.dispatcherCommandedState(requestedState); 300 } 301 302// If it doesn't exist, this returns OUTOFCORRESPONDENCE, else return it's present state: 303// NOTE: IF a preconditioned input was available, it OVERRIDES actual Switch Direction Lever (which is ignored in this case). 304 private int getSwitchDirectionLeverRequestedState(boolean allowMergeInPreconditioning) { 305 if (_mSwitchDirectionLever == null) return CTCConstants.OUTOFCORRESPONDENCE; 306 if (allowMergeInPreconditioning && _mPreconditioningData._mCodeButtonPressed) { // We can check and it is available: 307 if (_mPreconditioningData._mSwitchDirectionLeverWas == CTCConstants.SWITCHNORMAL 308 || _mPreconditioningData._mSwitchDirectionLeverWas == CTCConstants.SWITCHREVERSED) { // Was valid: 309 return _mPreconditioningData._mSwitchDirectionLeverWas; 310 } 311 } 312 return _mSwitchDirectionLever.getPresentState(); 313 } 314 315// If it doesn't exist, this returns true. 316 private boolean switchDirectionIndicatorsInCorrespondence() { 317 if (_mSwitchDirectionIndicators != null) return _mSwitchDirectionIndicators.inCorrespondence(); 318 return true; 319 } 320 321 private void possiblyAllowSignalDirectionChange() { 322 if (allowSignalDirectionChangePart1()) { 323 int presentSignalDirectionLever = getCurrentSignalDirectionLever(true); 324 int presentSignalDirectionIndicatorsDirection = _mSignalDirectionIndicators.getPresentDirection(); // Object always exists! 325 boolean requestedChangeInSignalDirection = (presentSignalDirectionLever != presentSignalDirectionIndicatorsDirection); 326// If Dispatcher is asking for a cleared signal direction: 327 if (presentSignalDirectionLever != CTCConstants.SIGNALSNORMAL) { 328 if (!requestedChangeInSignalDirection) return; // If presentSignalDirectionLever is the same as the current state, DO NOTHING! 329 } 330// If user is trying to change direction, FORCE to "SIGNALSNORMAL" per Rick Moser response of 6/29/16: 331 if (presentSignalDirectionLever == CTCConstants.LEFTTRAFFIC && presentSignalDirectionIndicatorsDirection == CTCConstants.RIGHTTRAFFIC) 332 presentSignalDirectionLever = CTCConstants.SIGNALSNORMAL; 333 else if (presentSignalDirectionLever == CTCConstants.RIGHTTRAFFIC && presentSignalDirectionIndicatorsDirection == CTCConstants.LEFTTRAFFIC) 334 presentSignalDirectionLever = CTCConstants.SIGNALSNORMAL; 335 336 if (allowSignalDirectionChangePart2(presentSignalDirectionLever)) { 337// Tell SignalDirectionIndicators what the current requested state is: 338 _mSignalDirectionIndicators.setPresentSignalDirectionLever(presentSignalDirectionLever); 339 _mSignalDirectionIndicators.codeButtonPressed(presentSignalDirectionLever, requestedChangeInSignalDirection); 340 } 341 } 342 } 343 344 private boolean allowSignalDirectionChangePart1() { 345// Safety Checks: 346 if (_mSignalDirectionLever == null) return false; 347// Rules: 348// 6/28/16: If the signal direction indicators are presently "OUTOFCORRESPONDENCE", IGNORE request, as we are presently working on a change: 349 if (!_mSignalDirectionIndicators.inCorrespondence()) return false; 350 if (!turnoutPresentlyLocked()) return false; 351 return true; // Allowed "so far". 352 } 353 354 private boolean allowSignalDirectionChangePart2(int presentSignalDirectionLever) { 355// Safety Checks: (none so far) 356// Rules: 357 if (presentSignalDirectionLever != CTCConstants.SIGNALSNORMAL) { 358// If asking for a route and these indicates an error (a conflict), DO NOTHING! 359 if (!trafficLockingValid(presentSignalDirectionLever)) return false; // Do NOTHING at this time! 360 } 361 return true; // Allowed 362 } 363 364 private boolean trafficLockingValid(int presentSignalDirectionLever) { 365// If asking for a route and it indicates an error (a conflict), DO NOTHING! 366 if (_mTrafficLocking != null) { 367 TrafficLockingInfo trafficLockingInfo = _mTrafficLocking.valid(presentSignalDirectionLever, _mFleeting.isFleetingEnabled()); 368 _mLockedRoute = trafficLockingInfo._mLockedRoute; // Can be null! This is the bread crumb trail when running time expires. 369 return trafficLockingInfo._mReturnStatus; 370 } 371 return true; // Valid 372 } 373 374 private void possiblyAllowLockChange() { 375 if (allowLockChange()) _mTurnoutLock.codeButtonPressed(); 376 } 377 378 private boolean allowLockChange() { 379// Safety checks: 380 if (_mTurnoutLock == null) return false; 381// Rules: 382// Degenerate case: If we ONLY have a lock toggle switch, code button and lock indicator then: 383// if these 3 are null and the provided signalDirectionIndocatorsObject is non functional, therefore ALWAYS allow it! 384// if (_mSignalDirectionIndicators.isNonfunctionalObject() && _mSignalDirectionLever == null && _mSwitchDirectionIndicators == null && _mSwitchDirectionLever == null) return true; 385// If this is a normal O.S. section, then if either is occupied, DO NOT allow unlock. 386// If this is NOT an O.S. section, but only a lock, AND the dispatcher is trying 387// to UNLOCK or LOCK this section, occupancy is not considered: 388 if (!_mTurnoutLockingOnlyEnabled) { // Normal O.S. section: 389 if (isEitherOSSectionOccupied()) return false; 390 } 391 if (!_mTurnoutLock.tryingToChangeLockStatus()) return false; 392 if (routeClearedAcross()) return false; 393 if (!_mSignalDirectionIndicators.signalsNormal()) return false; 394 if (!switchDirectionIndicatorsInCorrespondence()) return false; 395 if (!areOSSensorsAvailableInRoutes()) return false; 396 return true; 397 } 398 399 private boolean routeClearedAcross() { 400 if (_mIndicationLockingSignals != null) return _mIndicationLockingSignals.routeClearedAcross(); 401 return false; // Default: Nothing to evaluate, nothing cleared thru! 402 } 403 404 private boolean turnoutPresentlyLocked() { 405 if (_mTurnoutLock == null) return true; // Doesn't exist, assume locked so that anything can be done to it. 406 return _mTurnoutLock.turnoutPresentlyLocked(); 407 } 408 409// For "isEitherOSSectionOccupied" and "isPrimaryOSSectionOccupied" below, 410// INCONSISTENT, UNKNOWN and OCCUPIED are all considered OCCUPIED(ACTIVE). 411 private boolean isEitherOSSectionOccupied() { 412 return _mOSSectionOccupiedExternalSensor.getKnownState() != Sensor.INACTIVE || _mOSSectionOccupiedExternalSensor2.getKnownState() != Sensor.INACTIVE; 413 } 414 415// See "isEitherOSSectionOccupied" comment. 416 private boolean isPrimaryOSSectionOccupied() { 417 return _mOSSectionOccupiedExternalSensor.getKnownState() != Sensor.INACTIVE; 418 } 419 420 private boolean areOSSensorsAvailableInRoutes() { 421 HashSet<Sensor> sensors = new HashSet<>(); 422 sensors.add(_mOSSectionOccupiedExternalSensor.getBean()); 423 if (_mOSSectionOccupiedExternalSensor2.valid()) sensors.add(_mOSSectionOccupiedExternalSensor2.getBean()); 424 return _mLockedRoutesManager.checkRoute(sensors, _mUserIdentifier, "Turnout Check"); 425 } 426}